coverage-report

Code coverage report for All files

npmtest-nodebb (v0.0.1)

Code coverage report for All files

Statements: 7.62% (1686 / 22112)      Branches: 1.89% (213 / 11246)      Functions: 0.99% (61 / 6173)      Lines: 7.57% (1657 / 21890)      Ignored: 26 statements, 1 function, 30 branches     

File Statements Branches Functions Lines
node-npmtest-nodebb/ 100% (153 / 153) 100% (126 / 126) 100% (28 / 28) 100% (153 / 153)
node-npmtest-nodebb/node_modules/nodebb/ 20.79% (84 / 404) 10% (19 / 190) 6.45% (4 / 62) 20.79% (84 / 404)
node-npmtest-nodebb/node_modules/nodebb/install/ 50.91% (56 / 110) 10.53% (4 / 38) 52.38% (11 / 21) 50.91% (56 / 110)
node-npmtest-nodebb/node_modules/nodebb/public/src/ 6.59% (41 / 622) 3.85% (16 / 416) 3.79% (5 / 132) 6.59% (41 / 622)
node-npmtest-nodebb/node_modules/nodebb/public/src/admin/ 3.49% (3 / 86) 0% (0 / 46) 0% (0 / 20) 3.49% (3 / 86)
node-npmtest-nodebb/node_modules/nodebb/public/src/client/ 6.5% (95 / 1461) 0% (0 / 710) 0% (0 / 317) 6.5% (95 / 1461)
node-npmtest-nodebb/node_modules/nodebb/public/src/modules/ 5.77% (97 / 1681) 1.55% (14 / 903) 1.45% (6 / 414) 4.6% (68 / 1478)
node-npmtest-nodebb/node_modules/nodebb/src/ 5.9% (229 / 3879) 0.62% (12 / 1932) 0.37% (4 / 1076) 5.91% (229 / 3876)
node-npmtest-nodebb/node_modules/nodebb/src/categories/ 7.3% (34 / 466) 0% (0 / 209) 0% (0 / 192) 7.3% (34 / 466)
node-npmtest-nodebb/node_modules/nodebb/src/controllers/ 5.26% (84 / 1596) 0% (0 / 1015) 0% (0 / 307) 5.26% (84 / 1596)
node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/ 6.33% (29 / 458) 0% (0 / 175) 0% (0 / 108) 6.33% (29 / 458)
node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/ 14.12% (83 / 588) 0% (0 / 189) 0% (0 / 169) 14.12% (83 / 588)
node-npmtest-nodebb/node_modules/nodebb/src/database/ 15.51% (29 / 187) 22% (22 / 100) 6.25% (2 / 32) 15.68% (29 / 185)
node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/ 2.72% (22 / 809) 0% (0 / 523) 0% (0 / 202) 2.72% (22 / 809)
node-npmtest-nodebb/node_modules/nodebb/src/database/redis/ 25% (6 / 24) 0% (0 / 6) 0% (0 / 7) 25% (6 / 24)
node-npmtest-nodebb/node_modules/nodebb/src/groups/ 6.3% (37 / 587) 0% (0 / 320) 0% (0 / 201) 6.34% (37 / 584)
node-npmtest-nodebb/node_modules/nodebb/src/messaging/ 6.36% (18 / 283) 0% (0 / 101) 0% (0 / 114) 6.36% (18 / 283)
node-npmtest-nodebb/node_modules/nodebb/src/meta/ 11.24% (79 / 703) 0% (0 / 300) 0% (0 / 182) 11.24% (79 / 703)
node-npmtest-nodebb/node_modules/nodebb/src/middleware/ 7.76% (35 / 451) 0% (0 / 266) 0% (0 / 101) 7.76% (35 / 451)
node-npmtest-nodebb/node_modules/nodebb/src/navigation/ 8.47% (5 / 59) 0% (0 / 16) 0% (0 / 15) 8.47% (5 / 59)
node-npmtest-nodebb/node_modules/nodebb/src/plugins/ 5.96% (17 / 285) 0% (0 / 151) 0% (0 / 65) 5.96% (17 / 285)
node-npmtest-nodebb/node_modules/nodebb/src/posts/ 6.55% (62 / 947) 0% (0 / 448) 0% (0 / 333) 6.57% (62 / 943)
node-npmtest-nodebb/node_modules/nodebb/src/privileges/ 4.32% (23 / 532) 0% (0 / 300) 0% (0 / 210) 4.34% (23 / 530)
node-npmtest-nodebb/node_modules/nodebb/src/rewards/ 12% (15 / 125) 0% (0 / 42) 0% (0 / 55) 12% (15 / 125)
node-npmtest-nodebb/node_modules/nodebb/src/routes/ 11.11% (59 / 531) 0% (0 / 152) 1.14% (1 / 88) 11.11% (59 / 531)
node-npmtest-nodebb/node_modules/nodebb/src/socket.io/ 4.68% (44 / 940) 0% (0 / 536) 0% (0 / 254) 4.68% (44 / 940)
node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/ 5% (16 / 320) 0% (0 / 134) 0% (0 / 89) 5% (16 / 320)
node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/ 6.77% (21 / 310) 0% (0 / 164) 0% (0 / 101) 6.77% (21 / 310)
node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/ 4.68% (11 / 235) 0% (0 / 115) 0% (0 / 75) 4.68% (11 / 235)
node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/ 7.28% (15 / 206) 0% (0 / 124) 0% (0 / 65) 7.28% (15 / 206)
node-npmtest-nodebb/node_modules/nodebb/src/topics/ 5.55% (81 / 1459) 0% (0 / 669) 0% (0 / 579) 5.57% (81 / 1455)
node-npmtest-nodebb/node_modules/nodebb/src/user/ 6.46% (99 / 1533) 0% (0 / 777) 0% (0 / 537) 6.46% (99 / 1532)
node-npmtest-nodebb/node_modules/nodebb/src/widgets/ 4.88% (4 / 82) 0% (0 / 53) 0% (0 / 22) 4.88% (4 / 82)
Code coverage report for node-npmtest-nodebb/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/

Statements: 100% (153 / 153)      Branches: 100% (126 / 126)      Functions: 100% (28 / 28)      Lines: 100% (153 / 153)      Ignored: 26 statements, 1 function, 30 branches     

All files » node-npmtest-nodebb/
File Statements Branches Functions Lines
example.js 100% (83 / 83) 100% (73 / 73) 100% (12 / 12) 100% (83 / 83)
lib.npmtest_nodebb.js 100% (16 / 16) 100% (14 / 14) 100% (3 / 3) 100% (16 / 16)
test.js 100% (54 / 54) 100% (39 / 39) 100% (13 / 13) 100% (54 / 54)
Code coverage report for node-npmtest-nodebb/example.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/example.js

Statements: 100% (83 / 83)      Branches: 100% (73 / 73)      Functions: 100% (12 / 12)      Lines: 100% (83 / 83)      Ignored: 26 statements, 1 function, 30 branches     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328                                                  2   2         2   2   2 2 2         1             2       2       2   2               1 2           2     2     2 2   1       2     1 1 1   1 1     1 1   1     1   2           1   1   1         1 2 2 3 3 3 3 1     3 3         3       1 3 1       1 1               1   1 1 1   1   1                                                                                                                                                                                       1 1                       1     6 6   1   2   1   2         1 1   1         1             1     1 1     1 1   1 1 1 1 1 1 1   1 1   1        
/*
example.js
 
quickstart example
 
instruction
    1. save this script as example.js
    2. run the shell command:
        $ npm install npmtest-nodebb && PORT=8081 node example.js
    3. play with the browser-demo on http://127.0.0.1:8081
*/
 
 
 
/* istanbul instrument in package npmtest_nodebb */
/*jslint
    bitwise: true,
    browser: true,
    maxerr: 8,
    maxlen: 96,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';
    var local;
 
 
 
    // run shared js-env code - init-before
    (function () {
        // init local
        local = {};
        // init modeJs
        local.modeJs = (function () {
            try {
                return typeof navigator.userAgent === 'string' &&
                    typeof document.querySelector('body') === 'object' &&
                    typeof XMLHttpRequest.prototype.open === 'function' &&
                    'browser';
            } catch (errorCaughtBrowser) {
                return module.exports &&
                    typeof process.versions.node === 'string' &&
                    typeof require('http').createServer === 'function' &&
                    'node';
            }
        }());
        // init global
        local.global = local.modeJs === 'browser'
            ? window
            : global;
        // init utility2_rollup
        local = local.global.utility2_rollup || (local.modeJs === 'browser'
            ? local.global.utility2_npmtest_nodebb
            : global.utility2_moduleExports);
        // export local
        local.global.local = local;
    }());
    switch (local.modeJs) {
 
 
 
    // init-after
    // run browser js-env code - init-after
    /* istanbul ignore next */
    case 'browser':
        local.testRunBrowser = function (event) {
            Eif (!event || (event &&
                    event.currentTarget &&
                    event.currentTarget.className &&
                    event.currentTarget.className.includes &&
                    event.currentTarget.className.includes('onreset'))) {
                // reset output
                Array.from(
                    document.querySelectorAll('body > .resettable')
                ).forEach(function (element) {
                    switch (element.tagName) {
                    case 'INPUT':
                    case 'TEXTAREA':
                        element.value = '';
                        break;
                    default:
                        element.textContent = '';
                    }
                });
            }
            switch (event && event.currentTarget && event.currentTarget.id) {
            case 'testRunButton1':
                // show tests
                Eif (document.querySelector('#testReportDiv1').style.display === 'none') {
                    document.querySelector('#testReportDiv1').style.display = 'block';
                    document.querySelector('#testRunButton1').textContent =
                        'hide internal test';
                    local.modeTest = true;
                    local.testRunDefault(local);
                // hide tests
                } else {
                    document.querySelector('#testReportDiv1').style.display = 'none';
                    document.querySelector('#testRunButton1').textContent = 'run internal test';
                }
                break;
            // custom-case
            default:
                break;
            }
            Iif (document.querySelector('#inputTextareaEval1') && (!event || (event &&
                    event.currentTarget &&
                    event.currentTarget.className &&
                    event.currentTarget.className.includes &&
                    event.currentTarget.className.includes('oneval')))) {
                // try to eval input-code
                try {
                    /*jslint evil: true*/
                    eval(document.querySelector('#inputTextareaEval1').value);
                } catch (errorCaught) {
                    console.error(errorCaught);
                }
            }
        };
        // log stderr and stdout to #outputTextareaStdout1
        ['error', 'log'].forEach(function (key) {
            console[key + '_original'] = console[key];
            console[key] = function () {
                var element;
                console[key + '_original'].apply(console, arguments);
                element = document.querySelector('#outputTextareaStdout1');
                Iif (!element) {
                    return;
                }
                // append text to #outputTextareaStdout1
                element.value += Array.from(arguments).map(function (arg) {
                    return typeof arg === 'string'
                        ? arg
                        : JSON.stringify(arg, null, 4);
                }).join(' ') + '\n';
                // scroll textarea to bottom
                element.scrollTop = element.scrollHeight;
            };
        });
        // init event-handling
        ['change', 'click', 'keyup'].forEach(function (event) {
            Array.from(document.querySelectorAll('.on' + event)).forEach(function (element) {
                element.addEventListener(event, local.testRunBrowser);
            });
        });
        // run tests
        local.testRunBrowser();
        break;
 
 
 
    // run node js-env code - init-after
    /* istanbul ignore next */
    case 'node':
        // export local
        module.exports = local;
        // require modules
        local.fs = require('fs');
        local.http = require('http');
        local.url = require('url');
        // init assets
        local.assetsDict = local.assetsDict || {};
        /* jslint-ignore-begin */
        local.assetsDict['/assets.index.template.html'] = '\
<!doctype html>\n\
<html lang="en">\n\
<head>\n\
<meta charset="UTF-8">\n\
<meta name="viewport" content="width=device-width, initial-scale=1">\n\
<title>{{env.npm_package_name}} (v{{env.npm_package_version}})</title>\n\
<style>\n\
/*csslint\n\
    box-sizing: false,\n\
    universal-selector: false\n\
*/\n\
* {\n\
    box-sizing: border-box;\n\
}\n\
body {\n\
    background: #dde;\n\
    font-family: Arial, Helvetica, sans-serif;\n\
    margin: 2rem;\n\
}\n\
body > * {\n\
    margin-bottom: 1rem;\n\
}\n\
.utility2FooterDiv {\n\
    margin-top: 20px;\n\
    text-align: center;\n\
}\n\
</style>\n\
<style>\n\
/*csslint\n\
*/\n\
textarea {\n\
    font-family: monospace;\n\
    height: 10rem;\n\
    width: 100%;\n\
}\n\
textarea[readonly] {\n\
    background: #ddd;\n\
}\n\
</style>\n\
</head>\n\
<body>\n\
<!-- utility2-comment\n\
<div id="ajaxProgressDiv1" style="background: #d00; height: 2px; left: 0; margin: 0; padding: 0; position: fixed; top: 0; transition: background 0.5s, width 1.5s; width: 25%;"></div>\n\
utility2-comment -->\n\
<h1>\n\
<!-- utility2-comment\n\
    <a\n\
        {{#if env.npm_package_homepage}}\n\
        href="{{env.npm_package_homepage}}"\n\
        {{/if env.npm_package_homepage}}\n\
        target="_blank"\n\
    >\n\
utility2-comment -->\n\
        {{env.npm_package_name}} (v{{env.npm_package_version}})\n\
<!-- utility2-comment\n\
    </a>\n\
utility2-comment -->\n\
</h1>\n\
<h3>{{env.npm_package_description}}</h3>\n\
<!-- utility2-comment\n\
<h4><a download href="assets.app.js">download standalone app</a></h4>\n\
<button class="onclick onreset" id="testRunButton1">run internal test</button><br>\n\
<div id="testReportDiv1" style="display: none;"></div>\n\
utility2-comment -->\n\
\n\
\n\
\n\
<label>stderr and stdout</label>\n\
<textarea class="resettable" id="outputTextareaStdout1" readonly></textarea>\n\
<!-- utility2-comment\n\
{{#if isRollup}}\n\
<script src="assets.app.js"></script>\n\
{{#unless isRollup}}\n\
utility2-comment -->\n\
<script src="assets.utility2.rollup.js"></script>\n\
<script src="jsonp.utility2._stateInit?callback=window.utility2._stateInit"></script>\n\
<script src="assets.npmtest_nodebb.rollup.js"></script>\n\
<script src="assets.example.js"></script>\n\
<script src="assets.test.js"></script>\n\
<!-- utility2-comment\n\
{{/if isRollup}}\n\
utility2-comment -->\n\
<div class="utility2FooterDiv">\n\
    [ this app was created with\n\
    <a href="https://github.com/kaizhu256/node-utility2" target="_blank">utility2</a>\n\
    ]\n\
</div>\n\
</body>\n\
</html>\n\
';
        /* jslint-ignore-end */
        Iif (local.templateRender) {
            local.assetsDict['/'] = local.templateRender(
                local.assetsDict['/assets.index.template.html'],
                {
                    env: local.objectSetDefault(local.env, {
                        npm_package_description: 'the greatest app in the world!',
                        npm_package_name: 'my-app',
                        npm_package_nameAlias: 'my_app',
                        npm_package_version: '0.0.1'
                    })
                }
            );
        } else {
            local.assetsDict['/'] = local.assetsDict['/assets.index.template.html']
                .replace((/\{\{env\.(\w+?)\}\}/g), function (match0, match1) {
                    // jslint-hack
                    String(match0);
                    switch (match1) {
                    case 'npm_package_description':
                        return 'the greatest app in the world!';
                    case 'npm_package_name':
                        return 'my-app';
                    case 'npm_package_nameAlias':
                        return 'my_app';
                    case 'npm_package_version':
                        return '0.0.1';
                    }
                });
        }
        // run the cli
        Eif (local.global.utility2_rollup || module !== require.main) {
            break;
        }
        local.assetsDict['/assets.example.js'] =
            local.assetsDict['/assets.example.js'] ||
            local.fs.readFileSync(__filename, 'utf8');
        // bug-workaround - long $npm_package_buildCustomOrg
        /* jslint-ignore-begin */
        local.assetsDict['/assets.npmtest_nodebb.rollup.js'] =
            local.assetsDict['/assets.npmtest_nodebb.rollup.js'] ||
            local.fs.readFileSync(
                local.npmtest_nodebb.__dirname + '/lib.npmtest_nodebb.js',
                'utf8'
            ).replace((/^#!/), '//');
        /* jslint-ignore-end */
        local.assetsDict['/favicon.ico'] = local.assetsDict['/favicon.ico'] || '';
        // if $npm_config_timeout_exit exists,
        // then exit this process after $npm_config_timeout_exit ms
        if (Number(process.env.npm_config_timeout_exit)) {
            setTimeout(process.exit, Number(process.env.npm_config_timeout_exit));
        }
        // start server
        if (local.global.utility2_serverHttp1) {
            break;
        }
        process.env.PORT = process.env.PORT || '8081';
        console.error('server starting on port ' + process.env.PORT);
        local.http.createServer(function (request, response) {
            request.urlParsed = local.url.parse(request.url);
            if (local.assetsDict[request.urlParsed.pathname] !== undefined) {
                response.end(local.assetsDict[request.urlParsed.pathname]);
                return;
            }
            response.statusCode = 404;
            response.end();
        }).listen(process.env.PORT);
        break;
    }
}());
 
 
Code coverage report for node-npmtest-nodebb/lib.npmtest_nodebb.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/lib.npmtest_nodebb.js

Statements: 100% (16 / 16)      Branches: 100% (14 / 14)      Functions: 100% (3 / 3)      Lines: 100% (16 / 16)      Ignored: none     

All files » node-npmtest-nodebb/ » lib.npmtest_nodebb.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55                      2   2         2   2   2 2 2         1             2       2   2   2 1   1 1 1          
/* istanbul instrument in package npmtest_nodebb */
/*jslint
    bitwise: true,
    browser: true,
    maxerr: 8,
    maxlen: 96,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';
    var local;
 
 
 
    // run shared js-env code - init-before
    (function () {
        // init local
        local = {};
        // init modeJs
        local.modeJs = (function () {
            try {
                return typeof navigator.userAgent === 'string' &&
                    typeof document.querySelector('body') === 'object' &&
                    typeof XMLHttpRequest.prototype.open === 'function' &&
                    'browser';
            } catch (errorCaughtBrowser) {
                return module.exports &&
                    typeof process.versions.node === 'string' &&
                    typeof require('http').createServer === 'function' &&
                    'node';
            }
        }());
        // init global
        local.global = local.modeJs === 'browser'
            ? window
            : global;
        // init utility2_rollup
        local = local.global.utility2_rollup || local;
        // init lib
        local.local = local.npmtest_nodebb = local;
        // init exports
        if (local.modeJs === 'browser') {
            local.global.utility2_npmtest_nodebb = local;
        } else {
            module.exports = local;
            module.exports.__dirname = __dirname;
            module.exports.module = module;
        }
    }());
}());
 
 
Code coverage report for node-npmtest-nodebb/test.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/test.js

Statements: 100% (54 / 54)      Branches: 100% (39 / 39)      Functions: 100% (13 / 13)      Lines: 100% (54 / 54)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197                      2   2         2   2   2 2 2         1             2     2     1       1     1   1     2           2 2   2           1           1           2 2   2           1             1       1     1             1             1 1     1             1 1 1 1 1 1     1         2 2     1             2 2     1             2 2     1             2 2     1             1 1       1 1        
/* istanbul instrument in package npmtest_nodebb */
/*jslint
    bitwise: true,
    browser: true,
    maxerr: 8,
    maxlen: 96,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';
    var local;
 
 
 
    // run shared js-env code - init-before
    (function () {
        // init local
        local = {};
        // init modeJs
        local.modeJs = (function () {
            try {
                return typeof navigator.userAgent === 'string' &&
                    typeof document.querySelector('body') === 'object' &&
                    typeof XMLHttpRequest.prototype.open === 'function' &&
                    'browser';
            } catch (errorCaughtBrowser) {
                return module.exports &&
                    typeof process.versions.node === 'string' &&
                    typeof require('http').createServer === 'function' &&
                    'node';
            }
        }());
        // init global
        local.global = local.modeJs === 'browser'
            ? window
            : global;
        switch (local.modeJs) {
        // re-init local from window.local
        case 'browser':
            local = local.global.utility2.objectSetDefault(
                local.global.utility2_rollup || local.global.local,
                local.global.utility2
            );
            break;
        // re-init local from example.js
        case 'node':
            local = (local.global.utility2_rollup || require('utility2'))
                .requireReadme();
            break;
        }
        // export local
        local.global.local = local;
    }());
 
 
 
    // run shared js-env code - function
    (function () {
        return;
    }());
    switch (local.modeJs) {
 
 
 
    // run browser js-env code - function
    case 'browser':
        break;
 
 
 
    // run node js-env code - function
    case 'node':
        break;
    }
 
 
 
    // run shared js-env code - init-after
    (function () {
        return;
    }());
    switch (local.modeJs) {
 
 
 
    // run browser js-env code - init-after
    case 'browser':
        local.testCase_browser_nullCase = local.testCase_browser_nullCase || function (
            options,
            onError
        ) {
        /*
         * this function will test browsers's null-case handling-behavior-behavior
         */
            onError(null, options);
        };
 
        // run tests
        local.nop(local.modeTest &&
            document.querySelector('#testRunButton1') &&
            document.querySelector('#testRunButton1').click());
        break;
 
 
 
    // run node js-env code - init-after
    /* istanbul ignore next */
    case 'node':
        local.testCase_buildApidoc_default = local.testCase_buildApidoc_default || function (
            options,
            onError
        ) {
        /*
         * this function will test buildApidoc's default handling-behavior-behavior
         */
            options = { modulePathList: module.paths };
            local.buildApidoc(options, onError);
        };
 
        local.testCase_buildApp_default = local.testCase_buildApp_default || function (
            options,
            onError
        ) {
        /*
         * this function will test buildApp's default handling-behavior-behavior
         */
            local.testCase_buildReadme_default(options, local.onErrorThrow);
            local.testCase_buildLib_default(options, local.onErrorThrow);
            local.testCase_buildTest_default(options, local.onErrorThrow);
            local.testCase_buildCustomOrg_default(options, local.onErrorThrow);
            options = [];
            local.buildApp(options, onError);
        };
 
        local.testCase_buildCustomOrg_default = local.testCase_buildCustomOrg_default ||
            function (options, onError) {
            /*
             * this function will test buildCustomOrg's default handling-behavior
             */
                options = {};
                local.buildCustomOrg(options, onError);
            };
 
        local.testCase_buildLib_default = local.testCase_buildLib_default || function (
            options,
            onError
        ) {
        /*
         * this function will test buildLib's default handling-behavior
         */
            options = {};
            local.buildLib(options, onError);
        };
 
        local.testCase_buildReadme_default = local.testCase_buildReadme_default || function (
            options,
            onError
        ) {
        /*
         * this function will test buildReadme's default handling-behavior-behavior
         */
            options = {};
            local.buildReadme(options, onError);
        };
 
        local.testCase_buildTest_default = local.testCase_buildTest_default || function (
            options,
            onError
        ) {
        /*
         * this function will test buildTest's default handling-behavior
         */
            options = {};
            local.buildTest(options, onError);
        };
 
        local.testCase_webpage_default = local.testCase_webpage_default || function (
            options,
            onError
        ) {
        /*
         * this function will test webpage's default handling-behavior
         */
            options = { modeCoverageMerge: true, url: local.serverLocalHost + '?modeTest=1' };
            local.browserTest(options, onError);
        };
 
        // run test-server
        local.testRunServer(local);
        break;
    }
}());
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/

Statements: 20.79% (84 / 404)      Branches: 10% (19 / 190)      Functions: 6.45% (4 / 62)      Lines: 20.79% (84 / 404)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/
File Statements Branches Functions Lines
Gruntfile.js 7.14% (3 / 42) 0% (0 / 24) 0% (0 / 4) 7.14% (3 / 42)
app.js 25.76% (51 / 198) 16.67% (17 / 102) 13.04% (3 / 23) 25.76% (51 / 198)
bcrypt.js 25% (4 / 16) 0% (0 / 6) 0% (0 / 5) 25% (4 / 16)
loader.js 14.78% (17 / 115) 4.08% (2 / 49) 5% (1 / 20) 14.78% (17 / 115)
minifier.js 27.27% (9 / 33) 0% (0 / 9) 0% (0 / 10) 27.27% (9 / 33)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/Gruntfile.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/Gruntfile.js

Statements: 7.14% (3 / 42)      Branches: 0% (0 / 24)      Functions: 0% (0 / 4)      Lines: 7.14% (3 / 42)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114    1             1           1                                                                                                                                                                                                    
"use strict";
 
var fork = require('child_process').fork,
	env = process.env,
	worker, updateWorker,
	incomplete = [],
	running = 0;
 
 
module.exports = function (grunt) {
	var args = [];
	if (!grunt.option('verbose')) {
		args.push('--log-level=info');
	}
 
	function update(action, filepath, target) {
		var updateArgs = args.slice(),
			fromFile = '',
			compiling = '',
			time = Date.now();
		
		if (target === 'lessUpdated_Client') {
			compiling = 'clientCSS';
		} else if (target === 'lessUpdated_Admin') {
			compiling = 'acpCSS';
		} else if (target === 'clientUpdated') {
			compiling = 'js';
		} else if (target === 'templatesUpdated') {
			compiling = 'tpl';
		} else if (target === 'serverUpdated') {
			// Do nothing, just restart
		}
 
		if (incomplete.indexOf(compiling) === -1) {
			incomplete.push(compiling);
		}
 
		updateArgs.push('--build');
		updateArgs.push(incomplete.join(','));
 
		worker.kill();
		if (updateWorker) {
			updateWorker.kill('SIGKILL');
		}
		updateWorker = fork('app.js', updateArgs, { env: env });
		++running;
		updateWorker.on('exit', function () {
			--running;
			if (running === 0) {
				worker = fork('app.js', args, { env: env });
				worker.on('message', function () {
					if (incomplete.length) {
						incomplete = [];
 
						if (grunt.option('verbose')) {
							grunt.log.writeln('NodeBB restarted in ' + (Date.now() - time) + ' ms');
						}
					}
				});
			}
		});
	}
 
	grunt.initConfig({
		watch: {
			lessUpdated_Client: {
				files: [
					'public/*.less',
					'node_modules/nodebb-*/*.less', 'node_modules/nodebb-*/**/*.less',
					'!node_modules/nodebb-*/node_modules/**',
					'!node_modules/nodebb-*/.git/**'
				]
			},
			lessUpdated_Admin: {
				files: ['public/**/*.less']
			},
			clientUpdated: {
				files: [
					'public/src/**/*.js',
					'node_modules/nodebb-*/*.js', 'node_modules/nodebb-*/**/*.js',
					'!node_modules/nodebb-*/node_modules/**',
					'node_modules/templates.js/lib/templates.js',
					'!node_modules/nodebb-*/.git/**'
				]
			},
			serverUpdated: {
				files: ['*.js', 'install/*.js', 'src/**/*.js']
			},
			templatesUpdated: {
				files: [
					'src/views/**/*.tpl',
					'node_modules/nodebb-*/*.tpl', 'node_modules/nodebb-*/**/*.tpl',
					'!node_modules/nodebb-*/node_modules/**',
					'!node_modules/nodebb-*/.git/**'
				]
			}
		}
	});
 
	grunt.loadNpmTasks('grunt-contrib-watch');
 
	if (grunt.option('skip')) {
		grunt.registerTask('default', ['watch:serverUpdated']);
	} else {
		grunt.registerTask('default', ['watch']);
	}
	
 
	env.NODE_ENV = 'development';
 
	worker = fork('app.js', args, { env: env });
	grunt.event.on('watch', update);
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/app.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/app.js

Statements: 25.76% (51 / 198)      Branches: 16.67% (17 / 102)      Functions: 13.04% (3 / 23)      Lines: 25.76% (51 / 198)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383                                            1 1   1 1 1 1 1 1   1   1 1     429 429                 1   1       1   1 1   1   1 1 1 1       1   1 1                             1 1   1       1             1 1 1       1 1 1   1       1           1                                                                                                                                                                                                                                       1                                                                                               1                                         1                                         1                                           1                     1                       1 1 1 1 1   1          
/*
	NodeBB - A better forum platform for the modern web
	https://github.com/NodeBB/NodeBB/
	Copyright (C) 2013-2016  NodeBB Inc.
 
	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
 
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
 
	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
 
"use strict";
/*global require, global, process*/
 
var nconf = require('nconf');
nconf.argv().env('__');
 
var url = require('url');
var async = require('async');
var winston = require('winston');
var path = require('path');
var pkg = require('./package.json');
var file = require('./src/file');
 
global.env = process.env.NODE_ENV || 'production';
 
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
	colorize: true,
	timestamp: function () {
		var date = new Date();
		return (!!nconf.get('json-logging')) ? date.toJSON() :	date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
	},
	level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose'),
	json: (!!nconf.get('json-logging')),
	stringify: (!!nconf.get('json-logging'))
});
 
 
// Alternate configuration file support
var	configFile = path.join(__dirname, '/config.json');
 
Iif (nconf.get('config')) {
	configFile = path.resolve(__dirname, nconf.get('config'));
}
 
var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
 
loadConfig();
versionCheck();
 
Eif (!process.send) {
	// If run using `node app`, log GNU copyright info along with server info
	winston.info('NodeBB v' + nconf.get('version') + ' Copyright (C) 2013-' + (new Date()).getFullYear() + ' NodeBB Inc.');
	winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
	winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
	winston.info('');
}
 
 
Iif (nconf.get('setup') || nconf.get('install')) {
	setup();
} else Eif (!configExists) {
	require('./install/web').install(nconf.get('port'));
} else if (nconf.get('upgrade')) {
	upgrade();
} else if (nconf.get('reset')) {
	require('./src/reset').reset();
} else if (nconf.get('activate')) {
	activate();
} else if (nconf.get('plugins')) {
	listPlugins();
} else if (nconf.get('build')) {
	require('./build').build(nconf.get('build'));
} else {
	start();
}
 
function loadConfig(callback) {
	winston.verbose('* using configuration stored in: %s', configFile);
 
	nconf.file({
		file: configFile
	});
 
	nconf.defaults({
		base_dir: __dirname,
		themes_path: path.join(__dirname, 'node_modules'),
		views_dir: path.join(__dirname, 'public/templates'),
		version: pkg.version
	});
 
	Eif (!nconf.get('isCluster')) {
		nconf.set('isPrimary', 'true');
		nconf.set('isCluster', 'false');
	}
 
	// Ensure themes_path is a full filepath
	nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
	nconf.set('core_templates_path', path.join(__dirname, 'src/views'));
	nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates'));
 
	Iif (nconf.get('url')) {
		nconf.set('url_parsed', url.parse(nconf.get('url')));
	}
 
	Iif (typeof callback === 'function') {
		callback();
	}
}
 
 
function start() {
	var db = require('./src/database');
 
	// nconf defaults, if not set in config
	if (!nconf.get('upload_path')) {
		nconf.set('upload_path', '/public/uploads');
	}
	if (!nconf.get('sessionKey')) {
		nconf.set('sessionKey', 'express.sid');
	}
	// Parse out the relative_url and other goodies from the configured URL
	var urlObject = url.parse(nconf.get('url'));
	var relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
	nconf.set('base_url', urlObject.protocol + '//' + urlObject.host);
	nconf.set('secure', urlObject.protocol === 'https:');
	nconf.set('use_port', !!urlObject.port);
	nconf.set('relative_path', relativePath);
	nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567);
	nconf.set('upload_url', nconf.get('upload_path').replace(/^\/public/, ''));
 
	if (nconf.get('isPrimary') === 'true') {
		winston.info('Time: %s', (new Date()).toString());
		winston.info('Initializing NodeBB v%s', nconf.get('version'));
 
 
		var host = nconf.get(nconf.get('database') + ':host'),
			storeLocation = host ? 'at ' + host + (host.indexOf('/') === -1 ? ':' + nconf.get(nconf.get('database') + ':port') : '') : '';
 
		winston.verbose('* using %s store %s', nconf.get('database'), storeLocation);
		winston.verbose('* using themes stored in: %s', nconf.get('themes_path'));
	}
 
	process.on('SIGTERM', shutdown);
	process.on('SIGINT', shutdown);
	process.on('SIGHUP', restart);
	process.on('message', function (message) {
		if (typeof message !== 'object') {
			return;
		}
		var meta = require('./src/meta');
 
		switch (message.action) {
			case 'reload':
				meta.reload();
			break;
		}
	});
 
	process.on('uncaughtException', function (err) {
		winston.error(err.stack);
		console.log(err.stack);
 
		require('./src/meta').js.killMinifier();
		shutdown(1);
	});
 
	async.waterfall([
		async.apply(db.init),
		async.apply(db.checkCompatibility),
		function (next) {
			require('./src/meta').configs.init(next);
		},
		function (next) {
			if (nconf.get('dep-check') === undefined || nconf.get('dep-check') !== false) {
				require('./src/meta').dependencies.check(next);
			} else {
				winston.warn('[init] Dependency checking skipped!');
				setImmediate(next);
			}
		},
		function (next) {
			require('./src/upgrade').check(next);
		},
		function (next) {
			var webserver = require('./src/webserver');
			require('./src/socket.io').init(webserver.server);
 
			if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) {
				require('./src/notifications').init();
				require('./src/user').startJobs();
			}
 
			webserver.listen(next);
		}
	], function (err) {
		if (err) {
			switch(err.message) {
				case 'schema-out-of-date':
					winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
					winston.warn('    ./nodebb upgrade');
					break;
				case 'dependencies-out-of-date':
					winston.warn('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:');
					winston.warn('    ./nodebb upgrade');
					break;
				case 'dependencies-missing':
					winston.warn('One or more of NodeBB\'s dependent packages are missing. Please run the following command to update them:');
					winston.warn('    ./nodebb upgrade');
					break;
				default:
					winston.error(err);
					break;
			}
 
			// Either way, bad stuff happened. Abort start.
			process.exit();
		}
 
		if (process.send) {
			process.send({
				action: 'listening'
			});
		}
	});
}
 
function setup() {
	winston.info('NodeBB Setup Triggered via Command Line');
 
	var install = require('./src/install');
	var build = require('./build');
 
	process.stdout.write('\nWelcome to NodeBB!\n');
	process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
	process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
 
	async.series([
		async.apply(install.setup),
		async.apply(loadConfig),
		async.apply(build.build, true)
	], function (err, data) {
		// Disregard build step data
		data = data[0];
 
		var separator = '     ';
		if (process.stdout.columns > 10) {
			for(var x = 0,cols = process.stdout.columns - 10; x < cols; x++) {
				separator += '=';
			}
		}
		process.stdout.write('\n' + separator + '\n\n');
 
		if (err) {
			winston.error('There was a problem completing NodeBB setup: ', err.message);
		} else {
			if (data.hasOwnProperty('password')) {
				process.stdout.write('An administrative user was automatically created for you:\n');
				process.stdout.write('    Username: ' + data.username + '\n');
				process.stdout.write('    Password: ' + data.password + '\n');
				process.stdout.write('\n');
			}
			process.stdout.write('NodeBB Setup Completed. Run \'./nodebb start\' to manually start your NodeBB server.\n');
 
			// If I am a child process, notify the parent of the returned data before exiting (useful for notifying
			// hosts of auto-generated username/password during headless setups)
			if (process.send) {
				process.send(data);
			}
		}
 
		process.exit();
	});
}
 
function upgrade() {
	var db = require('./src/database');
	var meta = require('./src/meta');
	var upgrade = require('./src/upgrade');
	var build = require('./build');
 
	async.series([
		async.apply(db.init),
		async.apply(meta.configs.init),
		async.apply(upgrade.upgrade),
		async.apply(build.build, true)
	], function (err) {
		if (err) {
			winston.error(err.stack);
			process.exit(1);
		} else {
			process.exit(0);
		}
	});
}
 
function activate() {
	var db = require('./src/database');
	db.init(function (err) {
		if (err) {
			winston.error(err.stack);
			process.exit(1);
		}
 
		var plugin = nconf.get('activate');
		if (plugin.indexOf('nodebb-') !== 0) {
			// Allow omission of `nodebb-plugin-`
			plugin = 'nodebb-plugin-' + plugin;
		}
 
		winston.info('Activating plugin `%s`', plugin);
		db.sortedSetAdd('plugins:active', 0, plugin, function (err) {
			process.exit(err ? 1 : 0);
		});
	});
}
 
function listPlugins() {
	require('./src/database').init(function (err) {
		if (err) {
			winston.error(err.stack);
			process.exit(1);
		}
 
		var db = require('./src/database');
 
		db.getSortedSetRange('plugins:active', 0, -1, function (err, plugins) {
			if (err) {
				winston.error(err.stack);
				process.exit(1);
			}
 
			winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - '));
			process.exit();
		});
	});
}
 
 
function shutdown(code) {
	winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.');
	require('./src/database').close();
	winston.info('[app] Database connection closed.');
	require('./src/webserver').server.close();
	winston.info('[app] Web server closed to connections.');
 
	winston.info('[app] Shutdown complete.');
	process.exit(code || 0);
}
 
function restart() {
	if (process.send) {
		winston.info('[app] Restarting...');
		process.send({
			action: 'restart'
		});
	} else {
		winston.error('[app] Could not restart server. Shutting down.');
		shutdown(1);
	}
}
 
function versionCheck() {
	var version = process.version.slice(1);
	var range = pkg.engines.node;
	var semver = require('semver');
	var compatible = semver.satisfies(version, range);
 
	Iif (!compatible) {
		winston.warn('Your version of Node.js is too outdated for NodeBB. Please update your version of Node.js.');
		winston.warn('Recommended ' + range.green + ', '.reset + version.yellow + ' provided\n'.reset);
	}
}
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/bcrypt.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/bcrypt.js

Statements: 25% (4 / 16)      Branches: 0% (0 / 6)      Functions: 0% (0 / 5)      Lines: 25% (4 / 16)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35      1       1               1                     1                
 
'use strict';
 
var bcrypt = require('bcryptjs'),
	async = require('async');
 
 
process.on('message', function (msg) {
	if (msg.type === 'hash') {
		hashPassword(msg.password, msg.rounds);
	} else if (msg.type === 'compare') {
		bcrypt.compare(msg.password, msg.hash, done);
	}
});
 
function hashPassword(password, rounds) {
	async.waterfall([
		function (next) {
			bcrypt.genSalt(parseInt(rounds, 10), next);
		},
		function (salt, next) {
			bcrypt.hash(password, salt, next);
		}
	], done);
}
 
function done(err, result) {
	if (err) {
		process.send({err: err.message});
		return process.disconnect();
	}
	process.send({result: result});
	process.disconnect();
}
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/loader.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/loader.js

Statements: 14.78% (17 / 115)      Branches: 4.08% (2 / 49)      Functions: 5% (1 / 20)      Lines: 14.78% (17 / 115)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230    1                     1       1                   1                           1                   1                                                                                   1                         1                                                             1                           1             1               1             1             1                         1 1                                                             1        
'use strict';
 
var	nconf = require('nconf'),
	fs = require('fs'),
	url = require('url'),
	path = require('path'),
	fork = require('child_process').fork,
 
	async = require('async'),
	logrotate = require('logrotate-stream'),
	file = require('./src/file'),
	pkg = require('./package.json');
 
nconf.argv().env().file({
	file: path.join(__dirname, '/config.json')
});
 
var	pidFilePath = __dirname + '/pidfile',
	output = logrotate({ file: __dirname + '/logs/output.log', size: '1m', keep: 3, compress: true }),
	silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false,
	numProcs,
	workers = [],
 
	Loader = {
		timesStarted: 0
	};
 
Loader.init = function (callback) {
	if (silent) {
		console.log = function () {
			var args = Array.prototype.slice.call(arguments);
			output.write(args.join(' ') + '\n');
		};
	}
 
	process.on('SIGHUP', Loader.restart);
	process.on('SIGUSR2', Loader.reload);
	process.on('SIGTERM', Loader.stop);
	callback();
};
 
Loader.displayStartupMessages = function (callback) {
	console.log('');
	console.log('NodeBB v' + pkg.version + ' Copyright (C) 2013-2014 NodeBB Inc.');
	console.log('This program comes with ABSOLUTELY NO WARRANTY.');
	console.log('This is free software, and you are welcome to redistribute it under certain conditions.');
	console.log('For the full license, please visit: http://www.gnu.org/copyleft/gpl.html');
	console.log('');
	callback();
};
 
Loader.addWorkerEvents = function (worker) {
 
	worker.on('exit', function (code, signal) {
		if (code !== 0) {
			if (Loader.timesStarted < numProcs * 3) {
				Loader.timesStarted++;
				if (Loader.crashTimer) {
					clearTimeout(Loader.crashTimer);
				}
				Loader.crashTimer = setTimeout(function () {
					Loader.timesStarted = 0;
				}, 10000);
			} else {
				console.log(numProcs * 3 + ' restarts in 10 seconds, most likely an error on startup. Halting.');
				process.exit();
			}
		}
 
		console.log('[cluster] Child Process (' + worker.pid + ') has exited (code: ' + code + ', signal: ' + signal + ')');
		if (!(worker.suicide || code === 0)) {
			console.log('[cluster] Spinning up another process...');
 
			forkWorker(worker.index, worker.isPrimary);
		}
	});
 
	worker.on('message', function (message) {
		if (message && typeof message === 'object' && message.action) {
			switch (message.action) {
				case 'restart':
					console.log('[cluster] Restarting...');
					Loader.restart();
				break;
				case 'reload':
					console.log('[cluster] Reloading...');
					Loader.reload();
				break;
			}
		}
	});
};
 
Loader.start = function (callback) {
	numProcs = getPorts().length;
	console.log('Clustering enabled: Spinning up ' + numProcs + ' process(es).\n');
 
	for (var x = 0; x < numProcs; ++x) {
		forkWorker(x, x === 0);
	}
 
	if (callback) {
		callback();
	}
};
 
function forkWorker(index, isPrimary) {
	var ports = getPorts();
	var args = [];
 
	if(!ports[index]) {
		return console.log('[cluster] invalid port for worker : ' + index + ' ports: ' + ports.length);
	}
 
	process.env.isPrimary = isPrimary;
	process.env.isCluster = ports.length > 1 ? true : false;
	process.env.port = ports[index];
 
	var worker = fork('app.js', args, {
		silent: silent,
		env: process.env
	});
 
	worker.index = index;
	worker.isPrimary = isPrimary;
 
	workers[index] = worker;
 
	Loader.addWorkerEvents(worker);
 
	if (silent) {
		var output = logrotate({ file: __dirname + '/logs/output.log', size: '1m', keep: 3, compress: true });
		worker.stdout.pipe(output);
		worker.stderr.pipe(output);
	}
}
 
function getPorts() {
	var _url = nconf.get('url');
	if (!_url) {
		console.log('[cluster] url is undefined, please check your config.json');
		process.exit();
	}
	var urlObject = url.parse(_url);
	var port = nconf.get('port') || nconf.get('PORT') || urlObject.port || 4567;
	if (!Array.isArray(port)) {
		port = [port];
	}
	return port;
}
 
Loader.restart = function () {
	killWorkers();
	nconf.remove('file');
	nconf.use('file', { file: path.join(__dirname, '/config.json') });
	Loader.start();
};
 
Loader.reload = function () {
	workers.forEach(function (worker) {
		worker.send({
			action: 'reload'
		});
	});
};
 
Loader.stop = function () {
	killWorkers();
 
	// Clean up the pidfile
	fs.unlinkSync(__dirname + '/pidfile');
};
 
function killWorkers() {
	workers.forEach(function (worker) {
		worker.suicide = true;
		worker.kill();
	});
}
 
Loader.notifyWorkers = function (msg, worker_pid) {
	worker_pid = parseInt(worker_pid, 10);
	workers.forEach(function (worker) {
		if (parseInt(worker.pid, 10) !== worker_pid) {
			try {
				worker.send(msg);
			} catch (e) {
				console.log('[cluster/notifyWorkers] Failed to reach pid ' + worker_pid);
			}
		}
	});
};
 
fs.open(path.join(__dirname, 'config.json'), 'r', function (err) {
	Iif (!err) {
		if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
			if (file.existsSync(pidFilePath)) {
				try {
					var	pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
					process.kill(pid, 0);
					process.exit();
				} catch (e) {
					fs.unlinkSync(pidFilePath);
				}
			}
 
			require('daemon')({
				stdout: process.stdout,
				stderr: process.stderr
			});
 
			fs.writeFile(__dirname + '/pidfile', process.pid);
		}
 
		async.series([
			Loader.init,
			Loader.displayStartupMessages,
			Loader.start
		], function (err) {
			if (err) {
				console.log('[loader] Error during startup: ' + err.message);
			}
		});
	} else {
		// No config detected, kickstart web installer
		var child = require('child_process').fork('app');
	}
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/minifier.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/minifier.js

Statements: 27.27% (9 / 33)      Branches: 0% (0 / 9)      Functions: 0% (0 / 10)      Lines: 27.27% (9 / 33)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82    1 1 1 1   1         1                                           1                           1                                   1                              
"use strict";
 
var uglifyjs = require('uglify-js');
var async = require('async');
var fs = require('fs');
var file = require('./src/file');
 
var Minifier = {
	js: {}
};
 
/* Javascript */
Minifier.js.minify = function (scripts, minify, callback) {
 
	scripts = scripts.filter(function (file) {
		return file && file.endsWith('.js');
	});
 
	async.filter(scripts, function (script, next) {
		file.exists(script, function (exists) {
			if (!exists) {
				console.warn('[minifier] file not found, ' + script);
			}
			next(exists);
		});
	}, function (scripts) {
		if (minify) {
			minifyScripts(scripts, callback);
		} else {
			concatenateScripts(scripts, callback);
		}
	});
};
 
process.on('message', function (payload) {
	switch(payload.action) {
	case 'js':
		Minifier.js.minify(payload.scripts, payload.minify, function (minified/*, sourceMap*/) {
			process.send({
				type: 'end',
				// sourceMap: sourceMap,
				minified: minified
			});
		});
		break;
	}
});
 
function minifyScripts(scripts, callback) {
	// The portions of code involving the source map are commented out as they're broken in UglifyJS2
	// Follow along here: https://github.com/mishoo/UglifyJS2/issues/700
	try {
		var minified = uglifyjs.minify(scripts, {
				// outSourceMap: "nodebb.min.js.map",
				compress: false
			});
 
		callback(minified.code/*, minified.map*/);
	} catch(err) {
		process.send({
			type: 'error',
			message: err.message
		});
	}
}
 
function concatenateScripts(scripts, callback) {
	async.map(scripts, fs.readFile, function (err, scripts) {
		if (err) {
			process.send({
				type: 'error',
				message: err.message
			});
			return;
		}
 
		scripts = scripts.join(require('os').EOL + ';');
 
		callback(scripts);
	});
}
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/install/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/install/

Statements: 50.91% (56 / 110)      Branches: 10.53% (4 / 38)      Functions: 52.38% (11 / 21)      Lines: 50.91% (56 / 110)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/install/
File Statements Branches Functions Lines
databases.js 19.44% (7 / 36) 0% (0 / 22) 0% (0 / 5) 19.44% (7 / 36)
web.js 66.22% (49 / 74) 25% (4 / 16) 68.75% (11 / 16) 66.22% (49 / 74)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/install/databases.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/install/databases.js

Statements: 19.44% (7 / 36)      Branches: 0% (0 / 22)      Functions: 0% (0 / 5)      Lines: 19.44% (7 / 36)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83    1 1 1   1         1                         1                                           1                                                                        
"use strict";
 
var async = require('async');
var prompt = require('prompt');
var winston = require('winston');
 
var questions = {
	redis: require('../src/database/redis').questions,
	mongo: require('../src/database/mongo').questions
};
 
module.exports = function (config, callback) {
	async.waterfall([
		function (next) {
			process.stdout.write('\n');
			winston.info('Now configuring ' + config.database + ' database:');
			getDatabaseConfig(config, next);
		},
		function (databaseConfig, next) {
			saveDatabaseConfig(config, databaseConfig, next);
		}
	], callback);
};
 
function getDatabaseConfig(config, callback) {
	if (!config) {
		return callback(new Error('aborted'));
	}
 
	if (config.database === 'redis') {
		if (config['redis:host'] && config['redis:port']) {
			callback(null, config);
		} else {
			prompt.get(questions.redis, callback);
		}
	} else if (config.database === 'mongo') {
		if (config['mongo:host'] && config['mongo:port']) {
			callback(null, config);
		} else {
			prompt.get(questions.mongo, callback);
		}
	} else {
		return callback(new Error('unknown database : ' + config.database));
	}
}
 
function saveDatabaseConfig(config, databaseConfig, callback) {
	if (!databaseConfig) {
		return callback(new Error('aborted'));
	}
 
	// Translate redis properties into redis object
	if (config.database === 'redis') {
		config.redis = {
			host: databaseConfig['redis:host'],
			port: databaseConfig['redis:port'],
			password: databaseConfig['redis:password'],
			database: databaseConfig['redis:database']
		};
 
		if (config.redis.host.slice(0, 1) === '/') {
			delete config.redis.port;
		}
	} else if (config.database === 'mongo') {
		config.mongo = {
			host: databaseConfig['mongo:host'],
			port: databaseConfig['mongo:port'],
			username: databaseConfig['mongo:username'],
			password: databaseConfig['mongo:password'],
			database: databaseConfig['mongo:database']
		};
	} else {
		return callback(new Error('unknown database : ' + config.database));
	}
 
	var allQuestions = questions.redis.concat(questions.mongo);
	for (var x = 0; x < allQuestions.length; x++) {
		delete config[allQuestions[x].name];
	}
 
	callback(null, config);
}
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/install/web.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/install/web.js

Statements: 66.22% (49 / 74)      Branches: 25% (4 / 16)      Functions: 68.75% (11 / 16)      Lines: 66.22% (49 / 74)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153    1 1 1 1 1 1 1 1 1 1 1   1       423 423         1 1             1 1 1   1 1 1 1 1       1 1 1         1 1 1       1 1 1 1     1                                         1                                           1                                     1 1 1       1 1       1         1 1 1 4       1     1  
"use strict";
 
var winston = require('winston');
var express = require('express');
var bodyParser = require('body-parser');
var fs = require('fs');
var path = require('path');
var less = require('less');
var async = require('async');
var uglify = require('uglify-js');
var nconf = require('nconf');
var app = express();
var server;
 
winston.add(winston.transports.File, {
	filename: 'logs/webinstall.log',
	colorize: true,
	timestamp: function () {
		var date = new Date();
		return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
	},
	level: 'verbose'
});
 
var web = {};
var scripts = [
	'public/vendor/xregexp/xregexp.js',
	'public/vendor/xregexp/unicode/unicode-base.js',
	'public/src/utils.js',
	'public/src/installer/install.js'
];
 
web.install = function (port) {
	port = port || 4567;
	winston.info('Launching web installer on port', port);
 
	app.use(express.static('public', {}));
	app.engine('tpl', require('templates.js').__express);
	app.set('view engine', 'tpl');
	app.set('views', path.join(__dirname, '../src/views'));
	app.use(bodyParser.urlencoded({
		extended: true
	}));
 
	async.parallel([compileLess, compileJS], function () {
		setupRoutes();
		launchExpress(port);
	});
};
 
 
function launchExpress(port) {
	server = app.listen(port, function () {
		winston.info('Web installer listening on http://%s:%s', '0.0.0.0', port);
	});
}
 
function setupRoutes() {
	app.get('/', welcome);
	app.post('/', install);
	app.post('/launch', launch);
}
 
function welcome(req, res) {
	var dbs = ['redis', 'mongo'];
	var databases = dbs.map(function (el) {
		return {
			name: el,
			questions: require('../src/database/' + el).questions
		};
	});
 
	var defaults = require('./data/defaults');
 
	res.render('install/index', {
		databases: databases,
		skipDatabaseSetup: !!nconf.get('database'),
		error: res.locals.error ? true : false,
		success: res.locals.success ? true : false,
		values: req.body,
		minimumPasswordLength: defaults.minimumPasswordLength
	});
}
 
function install(req, res) {
	for (var i in req.body) {
		if (req.body.hasOwnProperty(i) && !process.env.hasOwnProperty(i)) {
			process.env[i.replace(':', '__')] = req.body[i];
		}
	}
 
	var child = require('child_process').fork('app', ['--setup'], {
		env: process.env
	});
 
	child.on('close', function (data) {
		if (data === 0) {
			res.locals.success = true;
		} else {
			res.locals.error = true;
		}
 
		welcome(req, res);
	});
}
 
function launch(req, res) {
	res.json({});
	server.close();
 
	var child = require('child_process').spawn('node', ['loader.js'], {
		detached: true,
		stdio: ['ignore', 'ignore', 'ignore']
	});
 
	process.stdout.write('\nStarting NodeBB\n');
	process.stdout.write('    "./nodebb stop" to stop the NodeBB server\n');
	process.stdout.write('    "./nodebb log" to view server output\n');
	process.stdout.write('    "./nodebb restart" to restart NodeBB\n');
 
	child.unref();
	process.exit(0);
 
}
 
function compileLess(callback) {
	fs.readFile(path.join(__dirname, '../public/less/install.less'), function (err, style) {
		Iif (err) {
			return winston.error('Unable to read LESS install file: ', err);
		}
 
		less.render(style.toString(), function (err, css) {
			Iif(err) {
				return winston.error('Unable to compile LESS: ', err);
			}
 
			fs.writeFile(path.join(__dirname, '../public/stylesheet.css'), css.css, callback);
		});
	});
}
 
function compileJS(callback) {
	var scriptPath = path.join(__dirname, '..');
	var result = uglify.minify(scripts.map(function (script) {
		return path.join(scriptPath, script);
	}));
 
 
	fs.writeFile(path.join(__dirname, '../public/nodebb.min.js'), result.code, callback);
}
 
module.exports = web;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/

Statements: 6.59% (41 / 622)      Branches: 3.85% (16 / 416)      Functions: 3.79% (5 / 132)      Lines: 6.59% (41 / 622)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/public/src/
File Statements Branches Functions Lines
ajaxify.js 3.26% (7 / 215) 1.1% (2 / 181) 0% (0 / 35) 3.26% (7 / 215)
overrides.js 4.12% (4 / 97) 10% (3 / 30) 0% (0 / 30) 4.12% (4 / 97)
require-config.js 100% (1 / 1) 100% (0 / 0) 100% (0 / 0) 100% (1 / 1)
sockets.js 15.94% (11 / 69) 10% (2 / 20) 11.11% (1 / 9) 15.94% (11 / 69)
utils.js 8.85% (17 / 192) 5.33% (8 / 150) 8% (4 / 50) 8.85% (17 / 192)
widgets.js 2.08% (1 / 48) 2.86% (1 / 35) 0% (0 / 8) 2.08% (1 / 48)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/ajaxify.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/ajaxify.js

Statements: 3.26% (7 / 215)      Branches: 1.1% (2 / 181)      Functions: 0% (0 / 35)      Lines: 3.26% (7 / 215)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408      2   2                                                                                                                                                                                                                                                                             1                                                                             1                                             1                                                                                                                                                                                                                                       1 1                                                                                                                                                                                
"use strict";
/*global app, bootbox, templates, socket, config, RELATIVE_PATH*/
 
var ajaxify = ajaxify || {};
 
$(document).ready(function () {
	var location = document.location || window.location;
	var rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : '');
	var apiXHR = null;
	var ajaxifyTimer;
 
	var translator;
	var retry = true;
	var previousBodyClass = '';
 
	// Dumb hack to fool ajaxify into thinking translator is still a global
	// When ajaxify is migrated to a require.js module, then this can be merged into the "define" call
	require(['translator'], function (_translator) {
		translator = _translator;
	});
 
	$(window).on('popstate', function (ev) {
		ev = ev.originalEvent;
 
		if (ev !== null && ev.state) {
			if (ev.state.url === null && ev.state.returnPath !== undefined) {
				window.history.replaceState({
					url: ev.state.returnPath
				}, ev.state.returnPath, config.relative_path + '/' + ev.state.returnPath);
			} else if (ev.state.url !== undefined) {
				ajaxify.go(ev.state.url, function () {
					$(window).trigger('action:popstate', {url: ev.state.url});
				}, true);
			}
		}
	});
 
	ajaxify.currentPage = null;
 
	ajaxify.go = function (url, callback, quiet) {
		if (!socket.connected) {
			if (ajaxify.reconnectAction) {
				$(window).off('action:reconnected', ajaxify.reconnectAction);
			}
			ajaxify.reconnectAction = function (e) {
				ajaxify.go(url, callback, quiet);
				$(window).off(e);
			};
			$(window).on('action:reconnected', ajaxify.reconnectAction);
		}
 
		// Abort subsequent requests if clicked multiple times within a short window of time
		if (ajaxifyTimer && (Date.now() - ajaxifyTimer) < 500) {
			return true;
		}
		ajaxifyTimer = Date.now();
 
		if (ajaxify.handleRedirects(url)) {
			return true;
		}
 
		app.leaveCurrentRoom();
 
		$(window).off('scroll');
 
		if ($('#content').hasClass('ajaxifying') && apiXHR) {
			apiXHR.abort();
		}
 
		if (!window.location.pathname.match(/\/(403|404)$/g)) {
			app.previousUrl = window.location.href;
		}
 
		url = ajaxify.start(url);
 
		// If any listeners alter url and set it to an empty string, abort the ajaxification
		if (url === null) {
			$(window).trigger('action:ajaxify.end', {url: url, tpl_url: ajaxify.data.template.name, title: ajaxify.data.title});
			return false;
		}
 
		previousBodyClass = ajaxify.data.bodyClass;
		$('#footer, #content').removeClass('hide').addClass('ajaxifying');
 
		ajaxify.loadData(url, function (err, data) {
 
			if (!err || (err && err.data && (parseInt(err.data.status, 10) !== 302 && parseInt(err.data.status, 10) !== 308))) {
				ajaxify.updateHistory(url, quiet);
			}
 
			if (err) {
				return onAjaxError(err, url, callback, quiet);
			}
 
			retry = true;
			app.template = data.template.name;
 
			require(['translator'], function (translator) {
				translator.load(config.defaultLang, data.template.name);
				renderTemplate(url, data.template.name, data, callback);
			});
		});
 
		return true;
	};
 
	ajaxify.handleRedirects = function (url) {
		url = ajaxify.removeRelativePath(url.replace(/\/$/, '')).toLowerCase();
		var isClientToAdmin = url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') !== 0;
		var isAdminToClient = !url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') === 0;
		var uploadsOrApi = url.startsWith('uploads') || url.startsWith('api');
		if (isClientToAdmin || isAdminToClient || uploadsOrApi) {
			window.open(RELATIVE_PATH + '/' + url, '_top');
			return true;
		}
		return false;
	};
 
 
	ajaxify.start = function (url) {
		url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, ''));
 
		var payload = {
			url: url
		};
 
		$(window).trigger('action:ajaxify.start', payload);
 
		return payload.url;
	};
 
	ajaxify.updateHistory = function (url, quiet) {
		ajaxify.currentPage = url.split(/[?#]/)[0];
		if (window.history && window.history.pushState) {
			window.history[!quiet ? 'pushState' : 'replaceState']({
				url: url
			}, url, RELATIVE_PATH + '/' + url);
		}
	};
 
	function onAjaxError(err, url, callback, quiet) {
		var data = err.data;
		var textStatus = err.textStatus;
 
		if (data) {
			var status = parseInt(data.status, 10);
			if (status === 403 || status === 404 || status === 500 || status === 502 || status === 503) {
				if (status === 502 && retry) {
					retry = false;
					ajaxifyTimer = undefined;
					return ajaxify.go(url, callback, quiet);
				}
				if (status === 502) {
					status = 500;
				}
				if (data.responseJSON) {
					data.responseJSON.config = config;
				}
 
				$('#footer, #content').removeClass('hide').addClass('ajaxifying');
				return renderTemplate(url, status.toString(), data.responseJSON || {}, callback);
			} else if (status === 401) {
				app.alertError('[[global:please_log_in]]');
				app.previousUrl = url;
				window.location.href = config.relative_path + '/login';
				return;
			} else if (status === 302 || status === 308) {
				if (data.responseJSON && data.responseJSON.external) {
					window.location.href = data.responseJSON.external;
				} else if (typeof data.responseJSON === 'string') {
					ajaxifyTimer = undefined;
					ajaxify.go(data.responseJSON.slice(1), callback, quiet);
				}
			}
		} else if (textStatus !== 'abort') {
			app.alertError(data.responseJSON.error);
		}
	}
 
	function renderTemplate(url, tpl_url, data, callback) {
		$(window).trigger('action:ajaxify.loadingTemplates', {});
 
		templates.parse(tpl_url, data, function (template) {
			translator.translate(template, function (translatedTemplate) {
				translatedTemplate = translator.unescape(translatedTemplate);
				$('body').removeClass(previousBodyClass).addClass(data.bodyClass);
				$('#content').html(translatedTemplate);
 
				ajaxify.end(url, tpl_url);
 
				if (typeof callback === 'function') {
					callback();
				}
 
				$('#content, #footer').removeClass('ajaxifying');
 
				app.refreshTitle(data.title);
			});
		});
	}
 
	ajaxify.end = function (url, tpl_url) {
		function done() {
			if (--count === 0) {
				$(window).trigger('action:ajaxify.end', {url: url, tpl_url: tpl_url, title: ajaxify.data.title});
			}
		}
		var count = 2;
 
		ajaxify.loadScript(tpl_url, done);
 
		ajaxify.widgets.render(tpl_url, url, done);
 
		$(window).trigger('action:ajaxify.contentLoaded', {url: url, tpl: tpl_url});
 
		app.processPage();
 
		var timeElapsed = Date.now() - ajaxifyTimer;
		if (config.environment === 'development' && !isNaN(timeElapsed)) {
			console.info('[ajaxify /' + url + '] Time elapsed:', timeElapsed + 'ms');
		}
	};
 
	ajaxify.parseData = function () {
		var dataEl = $('#ajaxify-data');
		if (dataEl.length) {
			ajaxify.data = JSON.parse(dataEl.text());
			dataEl.remove();
		}
	};
 
	ajaxify.removeRelativePath = function (url) {
		if (url.startsWith(RELATIVE_PATH.slice(1))) {
			url = url.slice(RELATIVE_PATH.length);
		}
		return url;
	};
 
	ajaxify.refresh = function (callback) {
		ajaxify.go(ajaxify.currentPage + window.location.search + window.location.hash, callback, true);
	};
 
	ajaxify.loadScript = function (tpl_url, callback) {
		var location = !app.inAdmin ? 'forum/' : '';
 
		if (tpl_url.startsWith('admin')) {
			location = '';
		}
		var data = {
			tpl_url: tpl_url,
			scripts: [location + tpl_url]
		};
 
		$(window).trigger('action:script.load', data);
 
		require(data.scripts, function (script) {
			if (script && script.init) {
				script.init();
			}
 
			if (callback) {
				callback();
			}
		});
	};
 
	ajaxify.loadData = function (url, callback) {
		url = ajaxify.removeRelativePath(url);
 
		$(window).trigger('action:ajaxify.loadingData', {url: url});
 
		apiXHR = $.ajax({
			url: RELATIVE_PATH + '/api/' + url,
			cache: false,
			headers: {
				'X-Return-To': app.previousUrl
			},
			success: function (data) {
				if (!data) {
					return;
				}
 
				ajaxify.data = data;
				data.config = config;
 
				$(window).trigger('action:ajaxify.dataLoaded', {url: url, data: data});
 
				callback(null, data);
			},
			error: function (data, textStatus) {
				if (data.status === 0 && textStatus === 'error') {
					data.status = 500;
				}
				callback({
					data: data,
					textStatus: textStatus
				});
			}
		});
	};
 
	ajaxify.loadTemplate = function (template, callback) {
		if (templates.cache[template]) {
			callback(templates.cache[template]);
		} else {
			$.ajax({
				url: RELATIVE_PATH + '/templates/' + template + '.tpl' + (config['cache-buster'] ? '?v=' + config['cache-buster'] : ''),
				type: 'GET',
				success: function (data) {
					callback(data.toString());
				},
				error: function (error) {
					throw new Error("Unable to load template: " + template + " (" + error.statusText + ")");
				}
			});
		}
	};
 
	function ajaxifyAnchors() {
		function hrefEmpty(href) {
			return href === undefined || href === '' || href === 'javascript:;';
		}
 
		var contentEl = document.getElementById('content');
 
		// Enhancing all anchors to ajaxify...
		$(document.body).on('click', 'a', function (e) {
			var _self = this;
			var process = function () {
				if (!e.ctrlKey && !e.shiftKey && !e.metaKey && e.which === 1) {
					if (internalLink) {
						var pathname = this.href.replace(rootUrl + RELATIVE_PATH + '/', '');
 
						// Special handling for urls with hashes
						if (window.location.pathname === this.pathname && this.hash.length) {
							window.location.hash = this.hash;
						} else {
							if (ajaxify.go(pathname)) {
								e.preventDefault();
							}
						}
					} else if (window.location.pathname !== '/outgoing') {
						if (config.openOutgoingLinksInNewTab && $.contains(contentEl, this)) {
							window.open(this.href, '_blank');
							e.preventDefault();
						} else if (config.useOutgoingLinksPage) {
							ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
							e.preventDefault();
						}
					}
				}
			};
 
			if (this.target !== '' || (this.protocol !== 'http:' && this.protocol !== 'https:')) {
				return;
			}
 
			var internalLink = utils.isInternalURI(this, window.location, RELATIVE_PATH);
 
			if ($(this).attr('data-ajaxify') === 'false') {
				if (!internalLink) {
					return;
				} else {
					return e.preventDefault();
				}
			}
 
			// Default behaviour for rss feeds
			if (internalLink && $(this).attr('href').endsWith('.rss')) {
				return;
			}
 
			if (hrefEmpty(this.href) || this.protocol === 'javascript:' || $(this).attr('href') === '#') {
				return e.preventDefault();
			}
 
			if (app.flags && app.flags.hasOwnProperty('_unsaved') && app.flags._unsaved === true) {
				translator.translate('[[global:unsaved-changes]]', function (text) {
					bootbox.confirm(text, function (navigate) {
						if (navigate) {
							app.flags._unsaved = false;
							process.call(_self);
						}
					});
				});
				return e.preventDefault();
			}
 
			process.call(_self);
		});
	}
 
	templates.registerLoader(ajaxify.loadTemplate);
 
	if (window.history && window.history.pushState) {
		// Progressive Enhancement, ajaxify available only to modern browsers
		ajaxifyAnchors();
	}
 
	app.load();
 
	$('[type="text/tpl"][data-template]').each(function () {
		templates.cache[$(this).attr('data-template')] = $('<div/>').html($(this).html()).text();
		$(this).parent().remove();
	});
 
});
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/overrides.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/overrides.js

Statements: 4.12% (4 / 97)      Branches: 10% (3 / 30)      Functions: 0% (0 / 30)      Lines: 4.12% (4 / 97)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177        1   1                                                                                                                                                 1                                                                               1                                                                                                                  
'use strict';
 
/* global bootbox */
 
var overrides = overrides || {};
 
Iif ('undefined' !== typeof window) {
 
	(function ($, undefined) {
		require(['translator'], function (translator) {
			$.fn.getCursorPosition = function () {
				var el = $(this).get(0);
				var pos = 0;
				if('selectionStart' in el) {
					pos = el.selectionStart;
				} else if('selection' in document) {
					el.focus();
					var Sel = document.selection.createRange();
					var SelLength = document.selection.createRange().text.length;
					Sel.moveStart('character', -el.value.length);
					pos = Sel.text.length - SelLength;
				}
				return pos;
			};
 
			$.fn.selectRange = function (start, end) {
				if(!end) {
					end = start;
				}
				return this.each(function () {
					if (this.setSelectionRange) {
						this.focus();
						this.setSelectionRange(start, end);
					} else if (this.createTextRange) {
						var range = this.createTextRange();
						range.collapse(true);
						range.moveEnd('character', end);
						range.moveStart('character', start);
						range.select();
					}
				});
			};
 
			//http://stackoverflow.com/questions/511088/use-javascript-to-place-cursor-at-end-of-text-in-text-input-element
			$.fn.putCursorAtEnd = function () {
				return this.each(function () {
					$(this).focus();
 
					if (this.setSelectionRange) {
						var len = $(this).val().length * 2;
						this.setSelectionRange(len, len);
					} else {
						$(this).val($(this).val());
					}
					this.scrollTop = 999999;
				});
			};
 
			$.fn.translateHtml = function (str) {
				return translate(this, 'html', str);
			};
 
			$.fn.translateText = function (str) {
				return translate(this, 'text', str);
			};
 
			$.fn.translateVal = function (str) {
				return translate(this, 'val', str);
			};
 
			$.fn.translateAttr = function (attr, str) {
				return this.each(function () {
					var el = $(this);
					translator.translate(str, function (translated) {
						el.attr(attr, translated);
					});
				});
			};
 
			function translate(elements, type, str) {
				return elements.each(function () {
					var el = $(this);
					translator.translate(str, function (translated) {
						el[type](translated);
					});
				});
			}
		});
	}(jQuery || {fn:{}}));
 
	(function () {
		// FIX FOR #1245 - https://github.com/NodeBB/NodeBB/issues/1245
		// from http://stackoverflow.com/questions/15931962/bootstrap-dropdown-disappear-with-right-click-on-firefox
		// obtain a reference to the original handler
		var _clearMenus = $._data(document, "events").click.filter(function (el) {
			return el.namespace === 'bs.data-api.dropdown' && el.selector === undefined;
		});
 
		if(_clearMenus.length) {
			_clearMenus = _clearMenus[0].handler;
		}
 
		// disable the old listener
		$(document)
			.off('click.data-api.dropdown', _clearMenus)
			.on('click.data-api.dropdown', function (e) {
				// call the handler only when not right-click
				if (e.button !== 2) {
					_clearMenus();
				}
			});
	}());
 
	overrides.overrideBootbox = function () {
		require(['translator'], function (translator) {
			var dialog = bootbox.dialog,
				prompt = bootbox.prompt,
				confirm = bootbox.confirm;
 
			function translate(modal) {
				var header = modal.find('.modal-header'),
					footer = modal.find('.modal-footer');
				translator.translate(header.html(), function (html) {
					header.html(html);
				});
				translator.translate(footer.html(), function (html) {
					footer.html(html);
				});
			}
 
			bootbox.dialog = function () {
				var modal = $(dialog.apply(this, arguments)[0]);
				translate(modal);
				return modal;
			};
 
			bootbox.prompt = function () {
				var modal = $(prompt.apply(this, arguments)[0]);
				translate(modal);
				return modal;
			};
 
			bootbox.confirm = function () {
				var modal = $(confirm.apply(this, arguments)[0]);
				translate(modal);
				return modal;
			};
		});
	};
 
	overrides.overrideTimeago = function () {
		var timeagoFn = $.fn.timeago;
		if (parseInt(config.timeagoCutoff, 10) === 0) {
			$.timeago.settings.cutoff = 1;
		} else if (parseInt(config.timeagoCutoff, 10) > 0) {
			$.timeago.settings.cutoff = 1000 * 60 * 60 * 24 * (parseInt(config.timeagoCutoff, 10) || 30);
		}
 
		$.fn.timeago = function () {
			var els = $(this);
 
			// Convert "old" format to new format (#5108)
			var options = { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' };
			var iso;
			els.each(function () {
				iso = this.getAttribute('title');
				this.setAttribute('datetime', iso);
				$(this).text(new Date(iso).toLocaleString(config.userLang.replace('_', '-'), options));
			});
 
			timeagoFn.apply(this, arguments);
		};
	};
 
}
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/require-config.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/require-config.js

Statements: 100% (1 / 1)      Branches: 100% (0 / 0)      Functions: 100% (0 / 0)      Lines: 100% (1 / 1)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 132                        
require.config({
	baseUrl: config.relative_path + "/src/modules",
	waitSeconds: 7,
	urlArgs: "v=" + config['cache-buster'],
	paths: {
		'forum': '../client',
		'admin': '../admin',
		'vendor': '../../vendor',
		'plugins': '../../plugins'
	}
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/sockets.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/sockets.js

Statements: 15.94% (11 / 69)      Branches: 10% (2 / 20)      Functions: 11.11% (1 / 9)      Lines: 15.94% (11 / 69)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129      2 2 2   2 2   2                                                           1                                                         1                                                                   1                             1         1          
'use strict';
/* globals config, io, ajaxify */
 
var app = app || {};
var socket;
app.isConnected = false;
 
(function () {
	var reconnecting = false;
 
	var ioParams = {
		reconnectionAttempts: config.maxReconnectionAttempts,
		reconnectionDelay: config.reconnectionDelay,
		transports: config.socketioTransports,
		path: config.relative_path + '/socket.io'
	};
 
	socket = io(config.websocketAddress, ioParams);
 
	socket.on('connect', onConnect);
 
	socket.on('reconnecting', onReconnecting);
 
	socket.on('disconnect', onDisconnect);
 
	socket.on('reconnect_failed', function () {
		// Wait ten times the reconnection delay and then start over
		setTimeout(socket.connect.bind(socket), parseInt(config.reconnectionDelay, 10) * 10);
	});
 
	socket.on('checkSession', function (uid) {
		if (parseInt(uid, 10) !== parseInt(app.user.uid, 10)) {
			app.handleInvalidSession();
		}
	});
 
	socket.on('event:banned', onEventBanned);
 
	socket.on('event:alert', app.alert);
 
	function onConnect() {
		app.isConnected = true;
 
		if (!reconnecting) {
			app.showMessages();
			$(window).trigger('action:connected');
		}
 
		if (reconnecting) {
			var reconnectEl = $('#reconnect');
			var reconnectAlert = $('#reconnect-alert');
 
			reconnectEl.tooltip('destroy');
			reconnectEl.html('<i class="fa fa-check"></i>');
			reconnectAlert.fadeOut(500);
			reconnecting = false;
 
			reJoinCurrentRoom();
 
			socket.emit('meta.reconnected');
 
			$(window).trigger('action:reconnected');
 
			setTimeout(function () {
				reconnectEl.removeClass('active').addClass('hide');
			}, 3000);
		}
	}
 
	function reJoinCurrentRoom() {
		var	url_parts = window.location.pathname.slice(config.relative_path.length).split('/').slice(1);
		var room;
 
		switch(url_parts[0]) {
			case 'user':
				room = 'user/' + (ajaxify.data ? ajaxify.data.theirid : 0);
			break;
			case 'topic':
				room = 'topic_' + url_parts[1];
			break;
			case 'category':
				room = 'category_' + url_parts[1];
			break;
			case 'recent':
				room = 'recent_topics';
			break;
			case 'unread':
				room = 'unread_topics';
			break;
			case 'popular':
				room = 'popular_topics';
			break;
			case 'admin':
				room = 'admin';
			break;
			case 'categories':
				room = 'categories';
			break;
		}
		app.currentRoom = '';
		app.enterRoom(room);
	}
 
	function onReconnecting() {
		reconnecting = true;
		var reconnectEl = $('#reconnect');
		var reconnectAlert = $('#reconnect-alert');
 
		if (!reconnectEl.hasClass('active')) {
			reconnectEl.html('<i class="fa fa-spinner fa-spin"></i>');
			reconnectAlert.fadeIn(500).removeClass('hide');
		}
 
		reconnectEl.addClass('active').removeClass('hide').tooltip({
			placement: 'bottom'
		});
	}
 
	function onDisconnect() {
		$(window).trigger('action:disconnected');
		app.isConnected = false;
	}
 
	function onEventBanned() {
		window.location.href = config.relative_path + '/';
	}
 
}());
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/utils.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/utils.js

Statements: 8.85% (17 / 192)      Branches: 5.33% (8 / 150)      Functions: 8% (4 / 50)      Lines: 8.85% (17 / 192)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 4871     1   1 1 1   1       1                   1   1 31   31                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               1                       1                           1 1 2       1                    
(function (module) {
	'use strict';
 
	var utils, fs, XRegExp;
 
	Eif ('undefined' === typeof window) {
		fs = require('fs');
		XRegExp = require('xregexp');
 
		process.profile = function (operation, start) {
			console.log('%s took %d milliseconds', operation, process.elapsedTimeSince(start));
		};
 
		process.elapsedTimeSince = function (start) {
			var diff = process.hrtime(start);
			return diff[0] * 1e3 + diff[1] / 1e6;
		};
 
	} else {
		XRegExp = window.XRegExp;
	}
 
 
	module.exports = utils = {
		generateUUID: function () {
			return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
				var r = Math.random() * 16 | 0,
					v = c === 'x' ? r : (r & 0x3 | 0x8);
				return v.toString(16);
			});
		},
 
		//Adapted from http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
		walk: function (dir, done) {
			var results = [];
 
			fs.readdir(dir, function (err, list) {
				if (err) {
					return done(err);
				}
				var pending = list.length;
				if (!pending) {
					return done(null, results);
				}
				list.forEach(function (file) {
					file = dir + '/' + file;
					fs.stat(file, function (err, stat) {
						if (err) {
							return done(err);
						}
 
						if (stat && stat.isDirectory()) {
							utils.walk(file, function (err, res) {
								if (err) {
									return done(err);
								}
 
								results = results.concat(res);
								if (!--pending) {
									done(null, results);
								}
							});
						} else {
							results.push(file);
							if (!--pending) {
								done(null, results);
							}
						}
					});
				});
			});
		},
 
		invalidUnicodeChars: XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'),
		invalidLatinChars: /[^\w\s\d\-_]/g,
		trimRegex: /^\s+|\s+$/g,
		collapseWhitespace: /\s+/g,
		collapseDash: /-+/g,
		trimTrailingDash: /-$/g,
		trimLeadingDash: /^-/g,
		isLatin: /^[\w\d\s.,\-@]+$/,
		languageKeyRegex: /\[\[[\w]+:.+\]\]/,
 
		//http://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/
		slugify: function (str, preserveCase) {
			if (!str) {
				return '';
			}
			str = str.replace(utils.trimRegex, '');
			if(utils.isLatin.test(str)) {
				str = str.replace(utils.invalidLatinChars, '-');
			} else {
				str = XRegExp.replace(str, utils.invalidUnicodeChars, '-');
			}
			str = !preserveCase ? str.toLocaleLowerCase() : str;
			str = str.replace(utils.collapseWhitespace, '-');
			str = str.replace(utils.collapseDash, '-');
			str = str.replace(utils.trimTrailingDash, '');
			str = str.replace(utils.trimLeadingDash, '');
			return str;
		},
 
		cleanUpTag: function (tag, maxLength) {
			if (typeof tag !== 'string' || !tag.length ) {
				return '';
			}
 
			tag = tag.trim().toLowerCase();
			// see https://github.com/NodeBB/NodeBB/issues/4378
			tag = tag.replace(/\u202E/gi, '');
			tag = tag.replace(/[,\/#!$%\^\*;:{}=_`<>'"~()?\|]/g, '');
			tag = tag.substr(0, maxLength || 15).trim();
			var matches = tag.match(/^[.-]*(.+?)[.-]*$/);
			if (matches && matches.length > 1) {
				tag = matches[1];
			}
			return tag;
		},
 
		removePunctuation: function (str) {
			return str.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`<>'"~()?]/g, '');
		},
 
		isEmailValid: function (email) {
			return typeof email === 'string' && email.length && email.indexOf('@') !== -1;
		},
 
		isUserNameValid: function (name) {
			return (name && name !== '' && (/^['"\s\-\+.*0-9\u00BF-\u1FFF\u2C00-\uD7FF\w]+$/.test(name)));
		},
 
		isPasswordValid: function (password) {
			return typeof password === 'string' && password.length;
		},
 
		isNumber: function (n) {
			return !isNaN(parseFloat(n)) && isFinite(n);
		},
 
		hasLanguageKey: function (input) {
			return utils.languageKeyRegex.test(input);
		},
 
		// shallow objects merge
		merge: function () {
			var result = {}, obj, keys;
			for (var i = 0; i < arguments.length; i++) {
				obj = arguments[i] || {};
				keys = Object.keys(obj);
				for (var j = 0; j < keys.length; j++) {
					result[keys[j]] = obj[keys[j]];
				}
			}
			return result;
		},
 
		fileExtension: function (path) {
			return ('' + path).split('.').pop();
		},
 
		extensionMimeTypeMap: {
			"bmp": "image/bmp",
			"cmx": "image/x-cmx",
			"cod": "image/cis-cod",
			"gif": "image/gif",
			"ico": "image/x-icon",
			"ief": "image/ief",
			"jfif": "image/pipeg",
			"jpe": "image/jpeg",
			"jpeg": "image/jpeg",
			"jpg": "image/jpeg",
			"png": "image/png",
			"pbm": "image/x-portable-bitmap",
			"pgm": "image/x-portable-graymap",
			"pnm": "image/x-portable-anymap",
			"ppm": "image/x-portable-pixmap",
			"ras": "image/x-cmu-raster",
			"rgb": "image/x-rgb",
			"svg": "image/svg+xml",
			"tif": "image/tiff",
			"tiff": "image/tiff",
			"xbm": "image/x-xbitmap",
			"xpm": "image/x-xpixmap",
			"xwd": "image/x-xwindowdump"
		},
 
		fileMimeType: function (path) {
			return utils.extensionToMimeType(utils.fileExtension(path));
		},
 
		extensionToMimeType: function (extension) {
			return utils.extensionMimeTypeMap[extension] || '*';
		},
 
		isRelativeUrl: function (url) {
			var firstChar = url.slice(0, 1);
			return (firstChar === '.' || firstChar === '/');
		},
 
		makeNumbersHumanReadable: function (elements) {
			elements.each(function () {
				$(this).html(utils.makeNumberHumanReadable($(this).attr('title')));
			});
		},
 
		makeNumberHumanReadable: function (num) {
			var n = parseInt(num, 10);
			if(!n) {
				return num;
			}
			if (n > 999999) {
				return (n / 1000000).toFixed(1) + 'm';
			}
			else if(n > 999) {
				return (n / 1000).toFixed(1) + 'k';
			}
			return n;
		},
 
		addCommasToNumbers: function (elements) {
			elements.each(function (index, element) {
				$(element).html(utils.addCommas($(element).html()));
			});
		},
 
		// takes a string like 1000 and returns 1,000
		addCommas: function (text) {
			return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
		},
 
		toISOString: function (timestamp) {
			if (!timestamp || !Date.prototype.toISOString) {
				return '';
			}
 
			return Date.prototype.toISOString ? new Date(parseInt(timestamp, 10)).toISOString() : timestamp;
		},
 
		tags : ['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr'],
 
		stripTags : ['abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'base', 'basefont',
			'bdi', 'bdo', 'big', 'blink', 'body', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup',
			'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed',
			'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
			'head', 'header', 'hr', 'html', 'iframe', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link',
			'map', 'mark', 'marquee', 'menu', 'meta', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option',
			'output', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select',
			'source', 'span', 'strike', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot',
			'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr'],
 
		escapeRegexChars: function (text) {
			return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
		},
 
		escapeHTML: function (raw) {
			return raw.replace(/&/gm,"&amp;").replace(/</gm,"&lt;").replace(/>/gm,"&gt;");
		},
 
		isAndroidBrowser: function () {
			// http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser
			var nua = navigator.userAgent;
			return ((nua.indexOf('Mozilla/5.0') > -1 && nua.indexOf('Android ') > -1 && nua.indexOf('AppleWebKit') > -1) && !(nua.indexOf('Chrome') > -1));
		},
 
		isTouchDevice: function () {
			return 'ontouchstart' in document.documentElement;
		},
 
		findBootstrapEnvironment: function () {
			//http://stackoverflow.com/questions/14441456/how-to-detect-which-device-view-youre-on-using-twitter-bootstrap-api
			var envs = ['xs', 'sm', 'md', 'lg'],
				$el = $('<div>');
 
			$el.appendTo($('body'));
 
			for (var i = envs.length - 1; i >= 0; i--) {
				var env = envs[i];
 
				$el.addClass('hidden-' + env);
				if ($el.is(':hidden')) {
					$el.remove();
					return env;
				}
			}
		},
 
		isMobile: function () {
			var env = utils.findBootstrapEnvironment();
			return ['xs', 'sm'].some(function (targetEnv) {
				return targetEnv === env;
			});
		},
 
		getHoursArray: function () {
			var currentHour = new Date().getHours(),
				labels = [];
 
			for (var i = currentHour, ii = currentHour - 24; i > ii; i--) {
				var hour = i < 0 ? 24 + i : i;
				labels.push(hour + ':00');
			}
 
			return labels.reverse();
		},
 
		getDaysArray: function (from) {
			var currentDay = new Date(from || Date.now()).getTime(),
				months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
				labels = [],
				tmpDate;
 
			for(var x = 29; x >= 0; x--) {
				tmpDate = new Date(currentDay - (1000 * 60 * 60 * 24 * x));
				labels.push(months[tmpDate.getMonth()] + ' ' + tmpDate.getDate());
			}
 
			return labels;
		},
 
		/* Retrieved from http://stackoverflow.com/a/7557433 @ 27 Mar 2016 */
		isElementInViewport: function (el) {
			//special bonus for those using jQuery
			if (typeof jQuery === "function" && el instanceof jQuery) {
				el = el[0];
			}
 
			var rect = el.getBoundingClientRect();
 
			return (
				rect.top >= 0 &&
				rect.left >= 0 &&
				rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
				rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
			);
		},
 
		// get all the url params in a single key/value hash
		params: function (options) {
			var a, hash = {}, params;
 
			options = options || {};
			options.skipToType = options.skipToType || {};
 
			if (options.url) {
				a = utils.urlToLocation(options.url);
			}
			params = (a ? a.search : window.location.search).substring(1).split("&");
 
			params.forEach(function (param) {
				var val = param.split('='),
					key = decodeURI(val[0]),
					value = options.skipToType[key] ? decodeURI(val[1]) : utils.toType(decodeURI(val[1]));
 
				if (key) {
					if (key.substr(-2, 2) === '[]') {
						key = key.slice(0, -2);
					}
					if (!hash[key]) {
						hash[key] = value;
					} else {
						if (!$.isArray(hash[key])) {
							hash[key] = [hash[key]];
						}
						hash[key].push(value);
					}
				}
			});
			return hash;
		},
 
		param: function (key) {
			return this.params()[key];
		},
 
		urlToLocation: function (url) {
			var a = document.createElement('a');
			a.href = url;
			return a;
		},
 
		// return boolean if string 'true' or string 'false', or if a parsable string which is a number
		// also supports JSON object and/or arrays parsing
		toType: function (str) {
			var type = typeof str;
			if (type !== 'string') {
				return str;
			} else {
				var nb = parseFloat(str);
				if (!isNaN(nb) && isFinite(str)) {
					return nb;
				}
				if (str === 'false') {
					return false;
				}
				if (str === 'true') {
					return true;
				}
 
				try {
					str = JSON.parse(str);
				} catch (e) {}
 
				return str;
			}
		},
 
		// Safely get/set chained properties on an object
		// set example: utils.props(A, 'a.b.c.d', 10) // sets A to {a: {b: {c: {d: 10}}}}, and returns 10
		// get example: utils.props(A, 'a.b.c') // returns {d: 10}
		// get example: utils.props(A, 'a.b.c.foo.bar') // returns undefined without throwing a TypeError
		// credits to github.com/gkindel
		props: function (obj, props, value) {
			if(obj === undefined) {
				obj = window;
			}
			if(props == null) {
				return undefined;
			}
			var i = props.indexOf('.');
			if( i == -1 ) {
				if(value !== undefined) {
					obj[props] = value;
				}
				return obj[props];
			}
			var prop = props.slice(0, i),
				newProps = props.slice(i + 1);
 
			if(props !== undefined && !(obj[prop] instanceof Object) ) {
				obj[prop] = {};
			}
 
			return utils.props(obj[prop], newProps, value);
		},
 
		isInternalURI: function (targetLocation, referenceLocation, relative_path) {
			return targetLocation.host === '' ||	// Relative paths are always internal links
				(
					targetLocation.host === referenceLocation.host && targetLocation.protocol === referenceLocation.protocol &&	// Otherwise need to check if protocol and host match
					(relative_path.length > 0 ? targetLocation.pathname.indexOf(relative_path) === 0 : true)	// Subfolder installs need this additional check
				);
		}
	};
 
	Iif (typeof String.prototype.startsWith != 'function') {
		String.prototype.startsWith = function (prefix) {
			if (this.length < prefix.length) {
				return false;
			}
			for (var i = prefix.length - 1; (i >= 0) && (this[i] === prefix[i]); --i) {
				continue;
			}
			return i < 0;
		};
	}
 
	Iif (typeof String.prototype.endsWith != 'function') {
		String.prototype.endsWith = function (suffix) {
			if (this.length < suffix.length) {
				return false;
			}
			var len = this.length;
			var suffixLen = suffix.length;
			for (var i = 1; (i <= suffixLen && this[len - i] === suffix[suffixLen - i]); ++i) {
				continue;
			}
			return i > suffixLen;
		};
	}
 
	Eif (typeof String.prototype.rtrim != 'function') {
		String.prototype.rtrim = function () {
			return this.replace(/\s+$/g, '');
		};
	}
 
	Iif ('undefined' !== typeof window) {
		window.utils = module.exports;
	}
 
}('undefined' === typeof module ? {
	module: {
		exports: {}
	}
} : module));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/widgets.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/widgets.js

Statements: 2.08% (1 / 48)      Branches: 2.86% (1 / 35)      Functions: 0% (0 / 8)      Lines: 2.08% (1 / 48)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91      2                                                                                                                                                                              
"use strict";
/*global ajaxify, templates, config, utils*/
 
(function (ajaxify) {
	ajaxify.widgets = {};
 
	ajaxify.widgets.reposition = function (location) {
		$('body [has-widget-class]').each(function () {
			var $this = $(this);
			if ($this.attr('has-widget-target') === location) {
				$this.removeClass();
				$this.addClass($this.attr('has-widget-class'));
			}
		});
	};
 
	ajaxify.widgets.render = function (template, url, callback) {
		callback = callback || function () {};
		if (template.match(/^admin/)) {
			return callback();
		}
 
		var widgetLocations = ['sidebar', 'footer', 'header'];
 
		$('#content [widget-area]').each(function () {
			var location = $(this).attr('widget-area');
			if ($.inArray(location, widgetLocations) === -1) {
				widgetLocations.push(location);
			}
		});
 
		$.get(config.relative_path + '/api/widgets/render' + (config['cache-buster'] ? '?v=' + config['cache-buster'] : ''), {
			locations: widgetLocations,
			template: template + '.tpl',
			url: url,
			isMobile: utils.isMobile()
		}, function (renderedAreas) {
			for (var x = 0; x < renderedAreas.length; ++x) {
				var renderedWidgets = renderedAreas[x].widgets;
				var location = renderedAreas[x].location;
				var html = '';
 
				for (var i = 0; i < renderedWidgets.length; ++i) {
					html += templates.parse(renderedWidgets[i].html, {});
				}
 
				var area = $('#content [widget-area="' + location + '"]');
 
				if (!area.length && window.location.pathname.indexOf('/admin') === -1 && renderedWidgets.length) {
					if (location === 'footer' && !$('#content [widget-area="footer"]').length) {
						$('#content').append($('<div class="row"><div widget-area="footer" class="col-xs-12"></div></div>'));
					} else if (location === 'sidebar' && !$('#content [widget-area="sidebar"]').length) {
						if ($('[component="account/cover"]').length) {
							$('[component="account/cover"]').nextAll().wrapAll($('<div class="row"><div class="col-lg-9 col-xs-12"></div><div widget-area="sidebar" class="col-lg-3 col-xs-12"></div></div></div>'));
						} else if ($('[component="groups/cover"]').length) {
							$('[component="groups/cover"]').nextAll().wrapAll($('<div class="row"><div class="col-lg-9 col-xs-12"></div><div widget-area="sidebar" class="col-lg-3 col-xs-12"></div></div></div>'));
						} else {
							$('#content > *').wrapAll($('<div class="row"><div class="col-lg-9 col-xs-12"></div><div widget-area="sidebar" class="col-lg-3 col-xs-12"></div></div></div>'));
						}
					} else if (location === 'header' && !$('#content [widget-area="header"]').length) {
						$('#content').prepend($('<div class="row"><div widget-area="header" class="col-xs-12"></div></div>'));
					}
 
					area = $('#content [widget-area="' + location + '"]');
				}
 
				area.html(html);
 
				if (renderedWidgets.length) {
					area.removeClass('hidden');
					ajaxify.widgets.reposition(location);
				}
			}
 
			var widgetAreas = $('#content [widget-area]');
			widgetAreas.find('img:not(.not-responsive)').addClass('img-responsive');
			widgetAreas.find('.timeago').timeago();
			widgetAreas.find('img[title].teaser-pic,img[title].user-img').each(function () {
				$(this).tooltip({
					placement: 'top',
					title: $(this).attr('title')
				});
			});
			$(window).trigger('action:widgets.loaded', {});
 
			callback(renderedAreas);
		});
	};
}(ajaxify || {}));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/admin/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/admin/

Statements: 3.49% (3 / 86)      Branches: 0% (0 / 46)      Functions: 0% (0 / 20)      Lines: 3.49% (3 / 86)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/public/src/admin/
File Statements Branches Functions Lines
settings.js 3.49% (3 / 86) 0% (0 / 46) 0% (0 / 20) 3.49% (3 / 86)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/admin/settings.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/admin/settings.js

Statements: 3.49% (3 / 86)      Branches: 0% (0 / 46)      Functions: 0% (0 / 20)      Lines: 3.49% (3 / 86)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194      2                                                                                                                                                                                                                                     1                                                     1                                                                                                
'use strict';
/*global define, app, socket, ajaxify */
 
define('admin/settings', ['uploader'], function (uploader) {
	var Settings = {};
 
	Settings.init = function () {
		console.warn('[deprecation] require(\'admin/settings\').init() has been deprecated, please call require(\'admin/settings\').prepare() directly instead.');
		Settings.prepare();
	};
 
	Settings.populateTOC = function () {
		$('.settings-header').each(function () {
			var header = $(this).text(),
				anchor = header.toLowerCase().replace(/ /g, '-').trim();
 
			$(this).prepend('<a name="' + anchor + '"></a>');
			$('.section-content ul').append('<li><a href="#' + anchor + '">' + header + '</a></li>');
		});
	};
 
	Settings.prepare = function (callback) {
		// Populate the fields on the page from the config
		var fields = $('#content [data-field]'),
			numFields = fields.length,
			saveBtn = $('#save'),
			revertBtn = $('#revert'),
			x, key, inputType, field;
 
		// Handle unsaved changes
		$(fields).on('change', function () {
			app.flags = app.flags || {};
			app.flags._unsaved = true;
		});
 
		for (x = 0; x < numFields; x++) {
			field = fields.eq(x);
			key = field.attr('data-field');
			inputType = field.attr('type');
			if (field.is('input')) {
				if (app.config[key]) {
					switch (inputType) {
					case 'text':
					case 'hidden':
					case 'password':
					case 'textarea':
					case 'number':
						field.val(app.config[key]);
						break;
 
					case 'checkbox':
						var checked = parseInt(app.config[key], 10) === 1;
						field.prop('checked', checked);
						field.parents('.mdl-switch').toggleClass('is-checked', checked);
						break;
					}
				}
			} else if (field.is('textarea')) {
				if (app.config.hasOwnProperty(key)) {
					field.val(app.config[key]);
				}
			} else if (field.is('select')) {
				if (app.config.hasOwnProperty(key)) {
					field.val(app.config[key]);
				}
			}
		}
 
		revertBtn.off('click').on('click', function () {
			ajaxify.refresh();
		});
 
		saveBtn.off('click').on('click', function (e) {
			e.preventDefault();
 
			saveFields(fields, function onFieldsSaved(err) {
				if (err) {
					return app.alert({
						alert_id: 'config_status',
						timeout: 2500,
						title: 'Changes Not Saved',
						message: 'NodeBB encountered a problem saving your changes',
						type: 'danger'
					});
				}
 
				app.flags._unsaved = false;
 
				app.alert({
					alert_id: 'config_status',
					timeout: 2500,
					title: 'Changes Saved',
					message: 'Your changes to the NodeBB configuration have been saved.',
					type: 'success'
				});
 
				$(window).trigger('action:admin.settingsSaved');
			});
		});
 
		handleUploads();
 
		$('#clear-sitemap-cache').off('click').on('click', function () {
			socket.emit('admin.settings.clearSitemapCache', function () {
				app.alertSuccess('Sitemap Cache Cleared!');
			});
			return false;
		});
 
		if (typeof callback === 'function') {
			callback();
		}
 
		setTimeout(function () {
			$(window).trigger('action:admin.settingsLoaded');
		}, 0);
	};
 
	function handleUploads() {
		$('#content input[data-action="upload"]').each(function () {
			var uploadBtn = $(this);
			uploadBtn.on('click', function () {
				uploader.show({
					title: uploadBtn.attr('data-title'),
					description: uploadBtn.attr('data-description'),
					route: uploadBtn.attr('data-route'),
					params: {},
					showHelp: uploadBtn.attr('data-help') ? uploadBtn.attr('data-help') === 1 : undefined,
					accept: uploadBtn.attr('data-accept')
				}, function (image) {
					// need to move these into template, ex data-callback
					if (ajaxify.currentPage === 'admin/general/sounds') {
						ajaxify.refresh();
					} else {
						$('#' + uploadBtn.attr('data-target')).val(image);
					}
				});
			});
		});
	}
 
	Settings.remove = function (key) {
		socket.emit('admin.config.remove', key);
	};
 
	function saveFields(fields, callback) {
		var data = {};
 
		fields.each(function () {
			var field = $(this);
			var key = field.attr('data-field'),
				value, inputType;
 
			if (field.is('input')) {
				inputType = field.attr('type');
				switch (inputType) {
				case 'text':
				case 'password':
				case 'hidden':
				case 'textarea':
				case 'number':
					value = field.val();
					break;
 
				case 'checkbox':
					value = field.prop('checked') ? '1' : '0';
					break;
				}
			} else if (field.is('textarea') || field.is('select')) {
				value = field.val();
			}
 
			data[key] = value;
		});
 
		socket.emit('admin.config.setMultiple', data, function (err) {
			if (err) {
				return callback(err);
			}
 
			for(var field in data) {
				if (data.hasOwnProperty(field)) {
					app.config[field] = data[field];
				}
			}
 
			callback();
		});
	}
 
	return Settings;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/

Statements: 6.5% (95 / 1461)      Branches: 0% (0 / 710)      Functions: 0% (0 / 317)      Lines: 6.5% (95 / 1461)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/public/src/client/
File Statements Branches Functions Lines
categories.js 7.89% (3 / 38) 0% (0 / 16) 0% (0 / 9) 7.89% (3 / 38)
category.js 3.76% (7 / 186) 0% (0 / 101) 0% (0 / 34) 3.76% (7 / 186)
categoryTools.js 11.97% (17 / 142) 0% (0 / 28) 0% (0 / 34) 11.97% (17 / 142)
chats.js 0.49% (1 / 203) 0% (0 / 82) 0% (0 / 50) 0.49% (1 / 203)
compose.js 14.29% (1 / 7) 0% (0 / 2) 0% (0 / 2) 14.29% (1 / 7)
footer.js 19.51% (8 / 41) 0% (0 / 25) 0% (0 / 10) 19.51% (8 / 41)
infinitescroll.js 3.85% (2 / 52) 0% (0 / 33) 0% (0 / 7) 3.85% (2 / 52)
login.js 3.03% (1 / 33) 0% (0 / 16) 0% (0 / 6) 3.03% (1 / 33)
notifications.js 5.56% (2 / 36) 0% (0 / 14) 0% (0 / 10) 5.56% (2 / 36)
popular.js 16.67% (1 / 6) 100% (0 / 0) 0% (0 / 2) 16.67% (1 / 6)
recent.js 4.08% (4 / 98) 0% (0 / 68) 0% (0 / 16) 4.08% (4 / 98)
register.js 7.26% (9 / 124) 0% (0 / 72) 0% (0 / 29) 7.26% (9 / 124)
reset.js 6.25% (1 / 16) 0% (0 / 6) 0% (0 / 4) 6.25% (1 / 16)
reset_code.js 5% (1 / 20) 0% (0 / 6) 0% (0 / 4) 5% (1 / 20)
search.js 7.37% (7 / 95) 0% (0 / 31) 0% (0 / 15) 7.37% (7 / 95)
tag.js 10% (2 / 20) 0% (0 / 14) 0% (0 / 5) 10% (2 / 20)
tags.js 7.89% (3 / 38) 0% (0 / 22) 0% (0 / 13) 7.89% (3 / 38)
topic.js 6.41% (10 / 156) 0% (0 / 111) 0% (0 / 29) 6.41% (10 / 156)
unread.js 7.35% (5 / 68) 0% (0 / 26) 0% (0 / 17) 7.35% (5 / 68)
users.js 12.2% (10 / 82) 0% (0 / 37) 0% (0 / 21) 12.2% (10 / 82)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/categories.js

Statements: 7.89% (3 / 38)      Branches: 0% (0 / 16)      Functions: 0% (0 / 9)      Lines: 7.89% (3 / 38)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76        2                                                   1                                                             1                            
'use strict';
 
/* globals define, socket, app, templates, ajaxify*/
 
define('forum/categories', ['components', 'translator'], function (components, translator) {
	var	categories = {};
 
	$(window).on('action:ajaxify.start', function (ev, data) {
		if (ajaxify.currentPage !== data.url) {
			socket.removeListener('event:new_post', categories.onNewPost);
		}
	});
 
	categories.init = function () {
		app.enterRoom('categories');
 
		socket.removeListener('event:new_post', categories.onNewPost);
		socket.on('event:new_post', categories.onNewPost);
 
		$('.category-header').tooltip({
			placement: 'bottom'
		});
	};
 
	categories.onNewPost = function (data) {
		if (data && data.posts && data.posts.length && data.posts[0].topic) {
			renderNewPost(data.posts[0].topic.cid, data.posts[0]);
		}
	};
 
	function renderNewPost(cid, post) {
		var category = components.get('categories/category', 'cid', cid);
		var numRecentReplies = category.attr('data-numRecentReplies');
		if (!numRecentReplies || !parseInt(numRecentReplies, 10)) {
			return;
		}
 
		var recentPosts = category.find('[component="category/posts"]');
		var insertBefore = recentPosts.first();
 
		parseAndTranslate([post], function (html) {
			html.hide();
			if(recentPosts.length === 0) {
				html.appendTo(category);
			} else {
				html.insertBefore(recentPosts.first());
			}
 
			html.fadeIn();
 
			app.createUserTooltips();
			html.find('.timeago').timeago();
 
			if (category.find('[component="category/posts"]').length > parseInt(numRecentReplies, 10)) {
				recentPosts.last().remove();
			}
 
			$(window).trigger('action:posts.loaded', {posts: [post]});
		});
	}
 
	function parseAndTranslate(posts, callback) {
		templates.parse('categories', '(categories.)?posts', {categories: {posts: posts}}, function (html) {
			translator.translate(html, function (translatedHTML) {
				translatedHTML = $(translatedHTML);
				translatedHTML.find('.post-content img:not(.not-responsive)').addClass('img-responsive');
 
				callback(translatedHTML);
			});
		});
	}
 
	return categories;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/category.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/category.js

Statements: 3.76% (7 / 186)      Branches: 0% (0 / 101)      Functions: 0% (0 / 34)      Lines: 3.76% (7 / 186)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360      2                                         1                                                                                   1                                                                                                                                                                                                                                     1                                                                                                                                   1                                         1                                                                 1                                                                                                                    
"use strict";
/* global define, config, templates, app, utils, ajaxify, socket */
 
define('forum/category', [
	'forum/infinitescroll',
	'share',
	'navigator',
	'forum/categoryTools',
	'sort',
	'components',
	'translator',
	'topicSelect',
	'forum/pagination'
], function (infinitescroll, share, navigator, categoryTools, sort, components, translator, topicSelect, pagination) {
	var Category = {};
 
	$(window).on('action:ajaxify.start', function (ev, data) {
		if (ajaxify.currentPage !== data.url) {
			navigator.disable();
 
			removeListeners();
		}
	});
 
	function removeListeners() {
		socket.removeListener('event:new_topic', Category.onNewTopic);
		categoryTools.removeListeners();
	}
 
	Category.init = function () {
		var	cid = ajaxify.data.cid;
 
		app.enterRoom('category_' + cid);
 
		share.addShareHandlers(ajaxify.data.name);
 
		socket.removeListener('event:new_topic', Category.onNewTopic);
		socket.on('event:new_topic', Category.onNewTopic);
 
		categoryTools.init(cid);
 
		sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug);
 
		if (!config.usePagination) {
			navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback);
		}
 
		enableInfiniteLoadingOrPagination();
 
		$('[component="category"]').on('click', '[component="topic/header"]', function () {
			var clickedIndex = $(this).parents('[data-index]').attr('data-index');
			$('[component="category/topic"]').each(function (index, el) {
				if ($(el).offset().top - $(window).scrollTop() > 0) {
					localStorage.setItem('category:' + cid + ':bookmark', $(el).attr('data-index'));
					localStorage.setItem('category:' + cid + ':bookmark:clicked', clickedIndex);
					return false;
				}
			});
		});
 
		handleIgnoreWatch(cid);
 
		$(window).trigger('action:topics.loaded', {topics: ajaxify.data.topics});
		$(window).trigger('action:category.loaded', {cid: ajaxify.data.cid});
	};
 
	function handleIgnoreWatch(cid) {
		$('[component="category/watching"], [component="category/ignoring"]').on('click', function () {
			var $this = $(this);
			var command = $this.attr('component') === 'category/watching' ? 'watch' : 'ignore';
 
			socket.emit('categories.' + command, cid, function (err) {
				if (err) {
					return app.alertError(err.message);
				}
 
				$('[component="category/watching/menu"]').toggleClass('hidden', command !== 'watch');
				$('[component="category/watching/check"]').toggleClass('fa-check', command === 'watch');
 
				$('[component="category/ignoring/menu"]').toggleClass('hidden', command !== 'ignore');
				$('[component="category/ignoring/check"]').toggleClass('fa-check', command === 'ignore');
 
				app.alertSuccess('[[category:' + command + '.message]]');
			});
		});
	}
 
	Category.toTop = function () {
		navigator.scrollTop(0);
	};
 
	Category.toBottom = function () {
		socket.emit('categories.getTopicCount', ajaxify.data.cid, function (err, count) {
			if (err) {
				return app.alertError(err.message);
			}
 
			navigator.scrollBottom(count - 1);
		});
	};
 
	Category.navigatorCallback = function (topIndex, bottomIndex, elementCount) {
		return bottomIndex;
	};
 
	$(window).on('action:popstate', function (ev, data) {
		if (data.url.startsWith('category/')) {
			var cid = data.url.match(/^category\/(\d+)/);
			if (cid && cid[1]) {
				cid = cid[1];
			}
			if (!cid) {
				return;
			}
 
			var bookmarkIndex = localStorage.getItem('category:' + cid + ':bookmark');
			var clickedIndex = localStorage.getItem('category:' + cid + ':bookmark:clicked');
 
			bookmarkIndex = Math.max(0, parseInt(bookmarkIndex, 10) || 0);
			clickedIndex = Math.max(0, parseInt(clickedIndex, 10) || 0);
			if (!parseInt(bookmarkIndex, 10)) {
				return;
			}
 
			if (config.usePagination) {
				var page = Math.ceil((parseInt(bookmarkIndex, 10) + 1) / config.topicsPerPage);
				if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) {
					pagination.loadPage(page, function () {
						Category.scrollToTopic(bookmarkIndex, clickedIndex, 400);
					});
				} else {
					Category.scrollToTopic(bookmarkIndex, clickedIndex, 400);
				}
			} else {
				if (bookmarkIndex === 0) {
					Category.highlightTopic(clickedIndex);
					return;
				}
 
				$('[component="category"]').empty();
 
				loadTopicsAfter(Math.max(0, bookmarkIndex - 1) + 1, 1, function () {
					Category.scrollToTopic(bookmarkIndex, clickedIndex, 0);
				});
			}
		}
	});
 
	Category.highlightTopic = function (topicIndex) {
		var highlight = components.get('category/topic', 'index', topicIndex);
 
		if (highlight.length && !highlight.hasClass('highlight')) {
			highlight.addClass('highlight');
			setTimeout(function () {
				highlight.removeClass('highlight');
			}, 5000);
		}
	};
 
	Category.scrollToTopic = function (bookmarkIndex, clickedIndex, duration, offset) {
		if (!bookmarkIndex) {
			return;
		}
 
		if (!offset) {
			offset = 0;
		}
 
		var scrollTo = components.get('category/topic', 'index', bookmarkIndex);
		var	cid = ajaxify.data.cid;
 
		if (scrollTo.length && cid) {
			$('html, body').animate({
				scrollTop: (scrollTo.offset().top - offset) + 'px'
			}, duration !== undefined ? duration : 400, function () {
				Category.highlightTopic(clickedIndex);
				navigator.update();
			});
		}
	};
 
	function enableInfiniteLoadingOrPagination() {
		if (!config.usePagination) {
			infinitescroll.init($('[component="category"]'), Category.loadMoreTopics);
		} else {
			navigator.disable();
		}
	}
 
	Category.onNewTopic = function (topic) {
		var	cid = ajaxify.data.cid;
		if (!topic || parseInt(topic.cid, 10) !== parseInt(cid, 10)) {
			return;
		}
 
		$(window).trigger('filter:categories.new_topic', topic);
 
		var editable = !!$('.thread-tools').length;
 
		templates.parse('category', 'topics', {
			privileges: {editable: editable},
			showSelect: editable,
			topics: [topic],
			template: {category: true}
		}, function (html) {
			translator.translate(html, function (translatedHTML) {
				var topic = $(translatedHTML),
					container = $('[component="category"]'),
					topics = $('[component="category/topic"]'),
					numTopics = topics.length;
 
				$('[component="category"]').removeClass('hidden');
				$('.category-sidebar').removeClass('hidden');
 
				var noTopicsWarning = $('#category-no-topics');
				if (noTopicsWarning.length) {
					noTopicsWarning.remove();
					ajaxify.widgets.render('category', window.location.pathname.slice(1));
				}
 
				if (numTopics > 0) {
					for (var x = 0; x < numTopics; x++) {
						var pinned = $(topics[x]).hasClass('pinned');
						if (pinned) {
							if(x === numTopics - 1) {
								topic.insertAfter(topics[x]);
							}
							continue;
						}
						topic.insertBefore(topics[x]);
						break;
					}
				} else {
					container.append(topic);
				}
 
				topic.hide().fadeIn('slow');
 
				topic.find('.timeago').timeago();
				app.createUserTooltips();
				updateTopicCount();
 
				$(window).trigger('action:categories.new_topic.loaded');
			});
		});
	};
 
	function updateTopicCount() {
		socket.emit('categories.getTopicCount', ajaxify.data.cid, function (err, topicCount) {
			if(err) {
				return app.alertError(err.message);
			}
			navigator.setCount(topicCount);
		});
	}
 
	Category.loadMoreTopics = function (direction) {
		if (!$('[component="category"]').length || !$('[component="category"]').children().length) {
			return;
		}
 
		var topics = $('[component="category/topic"]');
		var afterEl = direction > 0 ? topics.last() : topics.first();
		var after = (parseInt(afterEl.attr('data-index'), 10) || 0) + 1;
 
		loadTopicsAfter(after, direction);
	};
 
	function loadTopicsAfter(after, direction, callback) {
		callback = callback || function () {};
		if (!utils.isNumber(after) || (after === 0 && components.get('category/topic', 'index', 0).length)) {
			return callback();
		}
 
		$(window).trigger('action:categories.loading');
		var params = utils.params();
		infinitescroll.loadMore('categories.loadMore', {
			cid: ajaxify.data.cid,
			after: after,
			direction: direction,
			author: params.author,
			tag: params.tag,
			categoryTopicSort: config.categoryTopicSort
		}, function (data, done) {
			if (data.topics && data.topics.length) {
				Category.onTopicsLoaded(data, direction, done);
			} else {
				done();
			}
 
			$(window).trigger('action:categories.loaded');
			callback();
		});
	}
 
 
	Category.onTopicsLoaded = function (data, direction, callback) {
		if (!data || !data.topics.length) {
			return callback();
		}
 
		function removeAlreadyAddedTopics(topics) {
			return topics.filter(function (topic) {
				return components.get('category/topic', 'tid', topic.tid).length === 0;
			});
		}
 
		data.topics = removeAlreadyAddedTopics(data.topics);
		if (!data.topics.length) {
			return callback();
		}
 
		data.showSelect = data.privileges.editable;
 
		var after, before;
		var topics = $('[component="category/topic"]');
 
		if (direction > 0 && topics.length) {
			after = topics.last();
		} else if (direction < 0 && topics.length) {
			before = topics.first();
		}
 
		app.parseAndTranslate('category', 'topics', data, function (html) {
			$('[component="category"]').removeClass('hidden');
			$('.category-sidebar').removeClass('hidden');
 
			$('#category-no-topics').remove();
 
			if (after) {
				html.insertAfter(after);
			} else if (before) {
				var height = $(document).height(),
				 	scrollTop = $(window).scrollTop();
 
				html.insertBefore(before);
 
				$(window).scrollTop(scrollTop + ($(document).height() - height));
			} else {
				$('[component="category"]').append(html);
			}
 
			if (!topicSelect.getSelectedTids().length) {
				infinitescroll.removeExtra($('[component="category/topic"]'), direction, 60);
			}
 
			html.find('.timeago').timeago();
			app.createUserTooltips();
			utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
 
			$(window).trigger('action:topics.loaded', {topics: data.topics});
 
			callback();
		});
	};
 
	return Category;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/categoryTools.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/categoryTools.js

Statements: 11.97% (17 / 142)      Branches: 0% (0 / 28)      Functions: 0% (0 / 34)      Lines: 11.97% (17 / 142)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241            2                                                                                                                                                                                                               1                                                     1       1               1               1                                     1                 1                 1       1       1       1       1           1             1           1       1              
 
'use strict';
 
/* globals define, app, socket, bootbox, ajaxify */
 
 
define('forum/categoryTools', ['forum/topic/move', 'topicSelect', 'components', 'translator'], function (move, topicSelect, components, translator) {
 
	var CategoryTools = {};
 
	CategoryTools.init = function (cid) {
		CategoryTools.cid = cid;
 
		topicSelect.init(updateDropdownOptions);
 
		components.get('topic/delete').on('click', function () {
			categoryCommand('delete', topicSelect.getSelectedTids());
			return false;
		});
 
		components.get('topic/restore').on('click', function () {
			categoryCommand('restore', topicSelect.getSelectedTids());
			return false;
		});
 
		components.get('topic/purge').on('click', function () {
			categoryCommand('purge', topicSelect.getSelectedTids());
			return false;
		});
 
		components.get('topic/lock').on('click', function () {
			var tids = topicSelect.getSelectedTids();
			if (tids.length) {
				socket.emit('topics.lock', {tids: tids, cid: CategoryTools.cid}, onCommandComplete);
			}
			return false;
		});
 
		components.get('topic/unlock').on('click', function () {
			var tids = topicSelect.getSelectedTids();
			if (tids.length) {
				socket.emit('topics.unlock', {tids: tids, cid: CategoryTools.cid}, onCommandComplete);
			}
			return false;
		});
 
		components.get('topic/pin').on('click', function () {
			var tids = topicSelect.getSelectedTids();
			if (tids.length) {
				socket.emit('topics.pin', {tids: tids, cid: CategoryTools.cid}, onCommandComplete);
			}
			return false;
		});
 
		components.get('topic/unpin').on('click', function () {
			var tids = topicSelect.getSelectedTids();
			if (tids.length) {
				socket.emit('topics.unpin', {tids: tids, cid: CategoryTools.cid}, onCommandComplete);
			}
			return false;
		});
 
		components.get('topic/mark-unread-for-all').on('click', function () {
			var tids = topicSelect.getSelectedTids();
			if (tids.length) {
				socket.emit('topics.markAsUnreadForAll', tids, function (err) {
					if (err) {
						return app.alertError(err.message);
					}
					app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
					tids.forEach(function (tid) {
						$('[component="category/topic"][data-tid="' + tid + '"]').addClass('unread');
					});
					onCommandComplete();
				});
			}
 
			return false;
		});
 
		components.get('topic/move').on('click', function () {
			var tids = topicSelect.getSelectedTids();
 
			if (tids.length) {
				move.init(tids, cid, onCommandComplete);
			}
			return false;
		});
 
		components.get('topic/move-all').on('click', function () {
			move.init(null, cid, function (err) {
				if (err) {
					return app.alertError(err.message);
				}
 
				ajaxify.refresh();
			});
		});
 
		CategoryTools.removeListeners();
		socket.on('event:topic_deleted', setDeleteState);
		socket.on('event:topic_restored', setDeleteState);
		socket.on('event:topic_purged', onTopicPurged);
		socket.on('event:topic_locked', setLockedState);
		socket.on('event:topic_unlocked', setLockedState);
		socket.on('event:topic_pinned', setPinnedState);
		socket.on('event:topic_unpinned', setPinnedState);
		socket.on('event:topic_moved', onTopicMoved);
	};
 
	function categoryCommand(command, tids) {
		if (!tids.length) {
			return;
		}
 
		translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function (msg) {
			bootbox.confirm(msg, function (confirm) {
				if (!confirm) {
					return;
				}
 
				socket.emit('topics.' + command, {tids: tids, cid: CategoryTools.cid}, onDeletePurgeComplete);
			});
		});
	}
 
	CategoryTools.removeListeners = function () {
		socket.removeListener('event:topic_deleted', setDeleteState);
		socket.removeListener('event:topic_restored', setDeleteState);
		socket.removeListener('event:topic_purged', onTopicPurged);
		socket.removeListener('event:topic_locked', setLockedState);
		socket.removeListener('event:topic_unlocked', setLockedState);
		socket.removeListener('event:topic_pinned', setPinnedState);
		socket.removeListener('event:topic_unpinned', setPinnedState);
		socket.removeListener('event:topic_moved', onTopicMoved);
	};
 
	function closeDropDown() {
		$('.thread-tools.open').find('.dropdown-toggle').trigger('click');
	}
 
	function onCommandComplete(err) {
		if (err) {
			return app.alertError(err.message);
		}
		closeDropDown();
		topicSelect.unselectAll();
	}
 
	function onDeletePurgeComplete(err) {
		if (err) {
			return app.alertError(err.message);
		}
		closeDropDown();
		updateDropdownOptions();
	}
 
	function updateDropdownOptions() {
 
		var tids = topicSelect.getSelectedTids();
		var isAnyDeleted = isAny(isTopicDeleted, tids);
		var areAllDeleted = areAll(isTopicDeleted, tids);
		var isAnyPinned = isAny(isTopicPinned, tids);
		var isAnyLocked = isAny(isTopicLocked, tids);
 
		components.get('topic/delete').toggleClass('hidden', isAnyDeleted);
		components.get('topic/restore').toggleClass('hidden', !isAnyDeleted);
		components.get('topic/purge').toggleClass('hidden', !areAllDeleted);
 
		components.get('topic/lock').toggleClass('hidden', isAnyLocked);
		components.get('topic/unlock').toggleClass('hidden', !isAnyLocked);
 
		components.get('topic/pin').toggleClass('hidden', isAnyPinned);
		components.get('topic/unpin').toggleClass('hidden', !isAnyPinned);
	}
 
	function isAny(method, tids) {
		for(var i = 0; i < tids.length; ++i) {
			if(method(tids[i])) {
				return true;
			}
		}
		return false;
	}
 
	function areAll(method, tids) {
		for(var i = 0; i < tids.length; ++i) {
			if(!method(tids[i])) {
				return false;
			}
		}
		return true;
	}
 
	function isTopicDeleted(tid) {
		return getTopicEl(tid).hasClass('deleted');
	}
 
	function isTopicLocked(tid) {
		return getTopicEl(tid).hasClass('locked');
	}
 
	function isTopicPinned(tid) {
		return getTopicEl(tid).hasClass('pinned');
	}
 
	function getTopicEl(tid) {
		return components.get('category/topic', 'tid', tid);
	}
 
	function setDeleteState(data) {
		var topic = getTopicEl(data.tid);
		topic.toggleClass('deleted', data.isDeleted);
		topic.find('[component="topic/locked"]').toggleClass('hide', !data.isDeleted);
	}
 
	function setPinnedState(data) {
		var topic = getTopicEl(data.tid);
		topic.toggleClass('pinned', data.isPinned);
		topic.find('[component="topic/pinned"]').toggleClass('hide', !data.isPinned);
		ajaxify.refresh();
	}
 
	function setLockedState(data) {
		var topic = getTopicEl(data.tid);
		topic.toggleClass('locked', data.isLocked);
		topic.find('[component="topic/locked"]').toggleClass('hide', !data.isLocked);
	}
 
	function onTopicMoved(data) {
		getTopicEl(data.tid).remove();
	}
 
	function onTopicPurged(data) {
		getTopicEl(data.tid).remove();
	}
 
	return CategoryTools;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/chats.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/chats.js

Statements: 0.49% (1 / 203)      Branches: 0% (0 / 82)      Functions: 0% (0 / 50)      Lines: 0.49% (1 / 203)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378        2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
'use strict';
 
/* globals define, app, ajaxify, utils, socket, templates */
 
define('forum/chats', [
	'components',
	'translator',
	'mousetrap',
	'forum/chats/recent',
	'forum/chats/search',
	'forum/chats/messages'
], function (components, translator, mousetrap, recentChats, search, messages) {
	var Chats = {
		initialised: false
	};
 
	var newMessage = false;
 
	Chats.init = function () {
		var env = utils.findBootstrapEnvironment();
 
		if (!Chats.initialised) {
			Chats.addSocketListeners();
			Chats.addGlobalEventListeners();
		}
 
		Chats.addEventListeners();
		Chats.createTagsInput($('[component="chat/messages"] .users-tag-input'), ajaxify.data);
		Chats.createAutoComplete($('[component="chat/input"]'));
 
		components.get('expanded-chat/controlsToggle').on('click', function () {
			components.get('expanded-chat/controls').toggleClass('hide');
		});
 
		if (env === 'md' || env === 'lg') {
			Chats.resizeMainWindow();
			Chats.addHotkeys();
		}
 
		messages.scrollToBottom($('.expanded-chat ul'));
 
		Chats.initialised = true;
 
		search.init();
 
		if (ajaxify.data.hasOwnProperty('roomId')) {
			components.get('chat/input').focus();
		}
	};
 
	Chats.addEventListeners = function () {
		$('[component="chat/recent"]').on('click', '[component="chat/leave"]', function () {
			Chats.leave($(this).parents('[data-roomid]'));
			return false;
		});
 
		$('[component="chat/recent"]').on('click', '[component="chat/recent/room"]', function () {
			Chats.switchChat($(this).attr('data-roomid'));
		});
 
		Chats.addSendHandlers(ajaxify.data.roomId, $('.chat-input'), $('.expanded-chat button[data-action="send"]'));
 
		$('[data-action="pop-out"]').on('click', function () {
 
			var text = components.get('chat/input').val();
			var roomId = ajaxify.data.roomId;
 
			if (app.previousUrl && app.previousUrl.match(/chats/)) {
				ajaxify.go('user/' + ajaxify.data.userslug + '/chats', function () {
					app.openChat(roomId, ajaxify.data.uid);
				}, true);
			} else {
				window.history.go(-1);
				app.openChat(roomId, ajaxify.data.uid);
			}
 
			$(window).one('action:chat.loaded', function () {
				components.get('chat/input').val(text);
			});
		});
 
		Chats.addEditDeleteHandler(components.get('chat/messages'), ajaxify.data.roomId);
 
		recentChats.init();
 
		Chats.addRenameHandler(ajaxify.data.roomId, $('[component="chat/room/name"]'));
		Chats.addScrollHandler(ajaxify.data.roomId, ajaxify.data.uid, $('.chat-content'));
	};
 
	Chats.addScrollHandler = function (roomId, uid, el) {
		var loading = false;
		el.off('scroll').on('scroll', function () {
			if (loading) {
				return;
			}
 
			var top = (el[0].scrollHeight - el.height()) * 0.1;
			if (el.scrollTop() >= top) {
				return;
			}
			loading = true;
			var start =  parseInt($('.chat-content').children('[data-index]').first().attr('data-index'), 10) + 1;
			socket.emit('modules.chats.getMessages', {roomId: roomId, uid: uid, start: start}, function (err, data) {
				if (err) {
					return app.alertError(err.message);
				}
				if (!data) {
					return;
				}
				messages.parseMessage(data, function (html) {
					var currentScrollTop = el.scrollTop();
					var previousHeight = el[0].scrollHeight;
					html = $(html);
					el.prepend(html);
					html.find('.timeago').timeago();
					html.find('img:not(.not-responsive)').addClass('img-responsive');
					el.scrollTop((el[0].scrollHeight - previousHeight) + currentScrollTop);
					loading = false;
				});
			});
		});
	};
 
	Chats.addEditDeleteHandler = function (element, roomId) {
		element.on('click', '[data-action="edit"]', function () {
			var messageId = $(this).parents('[data-mid]').attr('data-mid');
			var inputEl = components.get('chat/input');
			messages.prepEdit(inputEl, messageId, roomId);
		}).on('click', '[data-action="delete"]', function () {
			var messageId = $(this).parents('[data-mid]').attr('data-mid');
			messages.delete(messageId, roomId);
		});
	};
 
	Chats.addHotkeys = function () {
		mousetrap.bind('ctrl+up', function () {
			var activeContact = $('.chats-list .bg-primary'),
				prev = activeContact.prev();
 
			if (prev.length) {
				Chats.switchChat(prev.attr('data-roomid'));
			}
		});
		mousetrap.bind('ctrl+down', function () {
			var activeContact = $('.chats-list .bg-primary'),
				next = activeContact.next();
 
			if (next.length) {
				Chats.switchChat(next.attr('data-roomid'));
			}
		});
		mousetrap.bind('up', function (e) {
			if (e.target === components.get('chat/input').get(0)) {
				// Retrieve message id from messages list
				var message = components.get('chat/messages').find('.chat-message[data-self="1"]').last();
				var lastMid = message.attr('data-mid');
				var inputEl = components.get('chat/input');
 
				messages.prepEdit(inputEl, lastMid, ajaxify.data.roomId);
			}
		});
	};
 
	Chats.addRenameHandler = function (roomId, inputEl) {
		var oldName = inputEl.val();
		inputEl.on('blur keypress', function (ev) {
			if (ev.type === 'keypress' && ev.keyCode !== 13) {
				return;
			}
			var newName = inputEl.val();
 
			if (oldName === newName) {
				return;
			}
			socket.emit('modules.chats.renameRoom', {roomId: roomId, newName: newName}, function (err) {
				if (err) {
					return app.alertError(err.message);
				}
				oldName = newName;
				inputEl.blur();
			});
		});
	};
 
	Chats.addSendHandlers = function (roomId, inputEl, sendEl) {
		inputEl.off('keypress').on('keypress', function (e) {
			if (e.which === 13 && !e.shiftKey) {
				messages.sendMessage(roomId, inputEl);
				return false;
			}
		});
 
		sendEl.off('click').on('click', function () {
			messages.sendMessage(roomId, inputEl);
			inputEl.focus();
			return false;
		});
	};
 
	Chats.createAutoComplete = function (element) {
		var data = {
			element: element,
			strategies: [],
			options: {
				zIndex: 20000,
				listPosition: function (position) {
					this.$el.css(this._applyPlacement(position));
					this.$el.css('position', 'absolute');
					return this;
				}
			}
		};
 
		$(window).trigger('chat:autocomplete:init', data);
		if (data.strategies.length) {
			data.element.textcomplete(data.strategies, data.options);
		}
	};
 
	Chats.createTagsInput = function (tagEl, data) {
		tagEl.tagsinput({
			confirmKeys: [13, 44],
			trimValue: true
		});
 
		if (data.users && data.users.length) {
			data.users.forEach(function (user) {
				tagEl.tagsinput('add', $('<div/>').html(user.username).text());
			});
		}
 
		tagEl.on('beforeItemAdd', function (event) {
			event.cancel = event.item === app.user.username;
		});
 
		tagEl.on('itemAdded', function (event) {
			if (event.item === app.user.username) {
				return;
			}
			socket.emit('modules.chats.addUserToRoom', {roomId: data.roomId, username: event.item}, function (err) {
				if (err) {
					app.alertError(err.message);
					tagEl.tagsinput('remove', event.item, {nouser: true});
				}
			});
		});
 
		tagEl.on('beforeItemRemove', function (event) {
			if (event.options && event.options.nouser) {
				return;
			}
 
			event.cancel = !data.isOwner || tagEl.tagsinput('items').length < 2;
			if (!data.owner) {
				return app.alertError('[[error:not-allowed]]');
			}
 
			if (tagEl.tagsinput('items').length < 2) {
				return app.alertError('[[error:cant-remove-last-user]]');
			}
		});
 
		tagEl.on('itemRemoved', function (event) {
			if (event.options && event.options.nouser) {
				return;
			}
			socket.emit('modules.chats.removeUserFromRoom', {roomId: data.roomId, username: event.item}, function (err) {
				if (err) {
					return app.alertError(err.message);
				}
			});
		});
 
		var input = $('.users-tag-container').find('.bootstrap-tagsinput input');
 
		require(['autocomplete'], function (autocomplete) {
			autocomplete.user(input);
		});
	};
 
	Chats.leave = function (el) {
		var roomId = el.attr('data-roomid');
		socket.emit('modules.chats.leave', roomId, function (err) {
			if (err) {
				return app.alertError(err.message);
			}
			if (parseInt(roomId, 10) === ajaxify.data.roomId) {
				ajaxify.go('user/' + ajaxify.data.userslug + '/chats');
			} else {
				el.remove();
			}
		});
	};
 
	Chats.switchChat = function (roomid) {
		ajaxify.go('user/' + ajaxify.data.userslug + '/chats/' + roomid);
	};
 
	Chats.addGlobalEventListeners = function () {
		$(window).on('resize', Chats.resizeMainWindow);
		$(window).on('mousemove keypress click', function () {
			if (newMessage && ajaxify.data.roomId) {
				socket.emit('modules.chats.markRead', ajaxify.data.roomId);
				newMessage = false;
			}
		});
	};
 
	Chats.addSocketListeners = function () {
		socket.on('event:chats.receive', function (data) {
			if (parseInt(data.roomId, 10) === parseInt(ajaxify.data.roomId, 10)) {
				newMessage = data.self === 0;
				data.message.self = data.self;
 
				messages.appendChatMessage($('.expanded-chat .chat-content'), data.message);
			} else {
				if (ajaxify.currentPage.startsWith("chats")) {
					var roomEl = $('[data-roomid=' + data.roomId + ']');
 
					if (roomEl.length > 0) {
						roomEl.addClass("unread");
					} else {
						var recentEl = components.get('chat/recent');
						templates.parse('partials/chats/recent_room', {
							rooms: { "roomId": data.roomId, "lastUser": data.message.fromUser, "usernames": data.message.fromUser.username, "unread": true }
						}, function (html) {
							translator.translate(html, function (translated) {
							    recentEl.prepend(translated);
							});
						});
					}
				}
			}
		});
 
		socket.on('event:user_status_change', function (data) {
			app.updateUserStatus($('.chats-list [data-uid="' + data.uid + '"] [component="user/status"]'), data.status);
		});
 
		messages.onChatMessageEdit();
 
		socket.on('event:chats.roomRename', function (data) {
			$('[component="chat/room/name"]').val($('<div/>').html(data.newName).text());
		});
	};
 
	Chats.resizeMainWindow = function () {
		var	messagesList = $('.expanded-chat .chat-content');
 
		if (messagesList.length) {
			var margin = $('.expanded-chat ul').outerHeight(true) - $('.expanded-chat ul').height();
			var inputHeight = $('.chat-input').outerHeight(true);
			var fromTop = messagesList.offset().top;
			var searchHeight = $('.chat-search').height();
			var searchListHeight = $('[component="chat/search/list"]').outerHeight(true) - $('[component="chat/search/list"]').height();
 
			messagesList.height($(window).height() - (fromTop + inputHeight + (margin * 4)));
			components.get('chat/recent').height($('.expanded-chat').height() - (searchHeight + searchListHeight));
			$('[component="chat/search/list"]').css('max-height', components.get('chat/recent').height() / 2 + 'px');
		}
 
		Chats.setActive();
	};
 
	Chats.setActive = function () {
		if (ajaxify.data.roomId) {
			socket.emit('modules.chats.markRead', ajaxify.data.roomId);
			$('.expanded-chat input').focus();
		}
		$('.chats-list li').removeClass('bg-primary');
		$('.chats-list li[data-roomid="' + ajaxify.data.roomId + '"]').addClass('bg-primary');
	};
 
 
	return Chats;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/compose.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/compose.js

Statements: 14.29% (1 / 7)      Branches: 0% (0 / 2)      Functions: 0% (0 / 2)      Lines: 14.29% (1 / 7)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21        2                                
'use strict';
 
/* globals define */
 
define('forum/compose', [], function () {
	var Compose = {};
 
	Compose.init = function () {
		var container = $('.composer');
 
		if (container.length) {
			$(window).trigger('action:composer.enhance', {
				container: container
			});
		}
	};
 
	return Compose;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/footer.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/footer.js

Statements: 19.51% (8 / 41)      Branches: 0% (0 / 25)      Functions: 0% (0 / 10)      Lines: 19.51% (8 / 41)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84      2           1           1           1           1     1                       1         1                                                                        
"use strict";
/*globals define, app, socket*/
 
define('forum/footer', ['notifications', 'chat', 'components', 'translator'], function (Notifications, Chat, components, translator) {
 
	Notifications.prepareDOM();
	Chat.prepareDOM();
	translator.prepareDOM();
 
	function updateUnreadTopicCount(count) {
		$('#unread-count i')
			.toggleClass('unread-count', count > 0)
			.attr('data-content', count > 99 ? '99+' : count);
	}
 
	function updateUnreadNewTopicCount(count) {
		$('#unread-new-count i')
			.toggleClass('unread-count', count > 0)
			.attr('data-content', count > 99 ? '99+' : count);
	}
 
	function updateUnreadChatCount(count) {
		components.get('chat/icon')
			.toggleClass('unread-count', count > 0)
			.attr('data-content', count > 99 ? '99+' : count);
	}
 
	function initUnreadTopics() {
		var unreadTopics = {};
 
		function onNewPost(data) {
			if (data && data.posts && data.posts.length) {
				var post = data.posts[0];
 
				if (parseInt(post.uid, 10) !== parseInt(app.user.uid, 10) && !unreadTopics[post.topic.tid]) {
					increaseUnreadCount();
					markTopicsUnread(post.topic.tid);
					unreadTopics[post.topic.tid] = true;
				}
			}
		}
 
		function increaseUnreadCount() {
			var count = parseInt($('#unread-count i').attr('data-content'), 10) + 1;
			updateUnreadTopicCount(count);
		}
 
		function markTopicsUnread(tid) {
			$('[data-tid="' + tid + '"]').addClass('unread');
		}
 
		$(window).on('action:ajaxify.end', function (ev, data) {
			if (data.url) {
				var tid = data.url.match(/^topic\/(\d+)/);
 
				if (tid && tid[1]) {
					delete unreadTopics[tid[1]];
				}
			}
		});
 
		socket.on('event:new_post', onNewPost);
	}
 
	if (app.user.uid) {
		socket.emit('user.getUnreadCounts', function (err, data) {
			if (err) {
				return app.alert(err.message);
			}
 
			updateUnreadTopicCount(data.unreadTopicCount);
			updateUnreadNewTopicCount(data.unreadNewTopicCount);
			updateUnreadChatCount(data.unreadChatCount);
			Notifications.updateNotifCount(data.unreadNotificationCount);
		});
	}
 
	socket.on('event:unread.updateCount', updateUnreadTopicCount);
	socket.on('event:unread.updateChatCount', updateUnreadChatCount);
 
	initUnreadTopics();
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/infinitescroll.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/infinitescroll.js

Statements: 3.85% (2 / 52)      Branches: 0% (0 / 33)      Functions: 0% (0 / 7)      Lines: 3.85% (2 / 52)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90        2                                       1                                                                                                                                  
'use strict';
 
/* globals define, socket, app */
 
define('forum/infinitescroll', function () {
 
	var scroll = {};
	var callback;
	var previousScrollTop = 0;
	var loadingMore	= false;
	var container;
 
	scroll.init = function (el, cb) {
		if (typeof el === 'function') {
			callback = el;
			container = $('body');
		} else {
			callback = cb;
			container = el || $('body');
		}
 
		$(window).off('scroll', onScroll).on('scroll', onScroll);
	};
 
	function onScroll() {
		if (loadingMore) {
			return;
		}
		var currentScrollTop = $(window).scrollTop();
		var wh = $(window).height();
		var viewportHeight = container.height() - wh;
		var offsetTop = container.offset() ? container.offset().top : 0;
		var scrollPercent = 100 * (currentScrollTop - offsetTop) / (viewportHeight <= 0 ? wh : viewportHeight);
 
		var top = 20, bottom = 80;
 
		var direction = currentScrollTop > previousScrollTop ? 1 : -1;
 
		if (scrollPercent < top && currentScrollTop < previousScrollTop) {
			callback(direction);
		} else if (scrollPercent > bottom && currentScrollTop > previousScrollTop) {
			callback(direction);
		} else if (scrollPercent < 0 && direction > 0 && viewportHeight < 0) {
			callback(direction);
		}
 
		previousScrollTop = currentScrollTop;
	}
 
	scroll.loadMore = function (method, data, callback) {
		if (loadingMore) {
			return;
		}
		loadingMore = true;
 
		var hookData = {method: method, data: data};
		$(window).trigger('action:infinitescroll.loadmore', hookData);
 
		socket.emit(hookData.method, hookData.data, function (err, data) {
			if (err) {
				loadingMore = false;
				return app.alertError(err.message);
			}
			callback(data, function () {
				loadingMore = false;
			});
		});
	};
 
	scroll.removeExtra = function (els, direction, count) {
		if (els.length <= count) {
			return;
		}
 
		var removeCount = els.length - count;
		if (direction > 0) {
			var height = $(document).height(),
				scrollTop = $(window).scrollTop();
 
			els.slice(0, removeCount).remove();
 
			$(window).scrollTop(scrollTop + ($(document).height() - height));
		} else {
			els.slice(els.length - removeCount).remove();
		}
	};
 
	return scroll;
});
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/login.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/login.js

Statements: 3.03% (1 / 33)      Branches: 0% (0 / 16)      Functions: 0% (0 / 6)      Lines: 3.03% (1 / 33)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76      2                                                                                                                                                
"use strict";
/* global define, app, config, RELATIVE_PATH */
 
define('forum/login', ['translator'], function (translator) {
	var	Login = {};
 
	Login.init = function () {
		var errorEl = $('#login-error-notify'),
			submitEl = $('#login'),
			formEl = $('#login-form');
 
		submitEl.on('click', function (e) {
			e.preventDefault();
 
			if (!$('#username').val() || !$('#password').val()) {
				errorEl.find('p').translateText('[[error:invalid-username-or-password]]');
				errorEl.show();
			} else {
				errorEl.hide();
 
				if (submitEl.hasClass('disabled')) {
					return;
				}
 
				submitEl.addClass('disabled');
 
				/*
					Set session refresh flag (otherwise the session check will trip and throw invalid session modal)
					We know the session is/will be invalid (uid mismatch) because the user is attempting a login
				*/
				app.flags = app.flags || {};
				app.flags._sessionRefresh = true;
 
				formEl.ajaxSubmit({
					headers: {
						'x-csrf-token': config.csrf_token
					},
					success: function (data, status) {
						window.location.href = data + '?loggedin';
					},
					error: function (data, status) {
						if (data.status === 403 && data.responseText === 'Forbidden') {
							window.location.href = config.relative_path + '/login?error=csrf-invalid';
						} else {
							errorEl.find('p').translateText(data.responseText);
							errorEl.show();
							submitEl.removeClass('disabled');
							app.flags._sessionRefresh = false;
 
							// Select the entire password if that field has focus
							if ($('#password:focus').size()) {
								$('#password').select();
							}
						}
					}
				});
			}
		});
 
		$('#login-error-notify button').on('click', function (e) {
			e.preventDefault();
			errorEl.hide();
			return false;
		});
 
		if ($('#content #username').attr('readonly')) {
			$('#content #password').val('').focus();
		} else {
			$('#content #username').focus();
		}
	};
 
	return Login;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/notifications.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/notifications.js

Statements: 5.56% (2 / 36)      Branches: 0% (0 / 14)      Functions: 0% (0 / 10)      Lines: 5.56% (2 / 36)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69        2                                                                           1                                                    
'use strict';
 
/* globals define, socket, app */
 
define('forum/notifications', ['components', 'notifications', 'forum/infinitescroll'], function (components, notifs, infinitescroll) {
	var Notifications = {};
 
	Notifications.init = function () {
		var listEl = $('.notifications-list');
		listEl.on('click', '[component="notifications/item/link"]', function () {
			var nid = $(this).parents('[data-nid]').attr('data-nid');
			socket.emit('notifications.markRead', nid, function (err) {
				if (err) {
					return app.alertError(err);
				}
 
				socket.emit('notifications.getCount', function (err, count) {
					if (err) {
						return app.alertError(err.message);
					}
 
					notifs.updateNotifCount(count);
				});
			});
		});
 
		$('.timeago').timeago();
 
		components.get('notifications/mark_all').on('click', function () {
			socket.emit('notifications.markAllRead', function (err) {
				if (err) {
					return app.alertError(err.message);
				}
 
				components.get('notifications/item').removeClass('unread');
				notifs.updateNotifCount(0);
			});
		});
 
		infinitescroll.init(loadMoreNotifications);
	};
 
	function loadMoreNotifications(direction) {
		if (direction < 0) {
			return;
		}
		var notifList = $('.notifications-list');
		infinitescroll.loadMore('notifications.loadMore', {
			after: notifList.attr('data-nextstart')
		}, function (data, done) {
			if (!data) {
				return done();
			}
			notifList.attr('data-nextstart', data.nextStart);
			if (!data.notifications || !data.notifications.length) {
				return done();
			}
			app.parseAndTranslate('notifications', 'notifications', {notifications: data.notifications}, function (html) {
				notifList.append(html);
				html.find('.timeago').timeago();
				done();
			});
		});
	}
 
	return Notifications;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/popular.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/popular.js

Statements: 16.67% (1 / 6)      Branches: 100% (0 / 0)      Functions: 0% (0 / 2)      Lines: 16.67% (1 / 6)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20        2                              
'use strict';
 
/* globals define, app, socket*/
 
define('forum/popular', ['components'], function (components) {
	var Popular = {};
 
	Popular.init = function () {
		app.enterRoom('popular_topics');
 
		components.get('popular/tab')
			.removeClass('active')
			.find('a[href="' + window.location.pathname + '"]')
			.parent().addClass('active');
	};
 
	return Popular;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/recent.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/recent.js

Statements: 4.08% (4 / 98)      Branches: 0% (0 / 68)      Functions: 0% (0 / 16)      Lines: 4.08% (4 / 98)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174        2                                                                       1                         1 1                                                                                                                                                                                                                                              
'use strict';
 
/* globals define, app, socket, utils, ajaxify, config */
 
define('forum/recent', ['forum/infinitescroll', 'components'], function (infinitescroll, components) {
	var	Recent = {};
 
	var newTopicCount = 0;
	var newPostCount = 0;
 
	$(window).on('action:ajaxify.start', function (ev, data) {
		if (ajaxify.currentPage !== data.url) {
			Recent.removeListeners();
		}
	});
 
	Recent.init = function () {
		app.enterRoom('recent_topics');
 
		Recent.watchForNewPosts();
 
		$('#new-topics-alert').on('click', function () {
			$(this).addClass('hide');
		});
 
		if (!config.usePagination) {
			infinitescroll.init(Recent.loadMoreTopics);
		}
 
		$(window).trigger('action:topics.loaded', {topics: ajaxify.data.topics});
	};
 
	Recent.watchForNewPosts = function () {
		newPostCount = 0;
		newTopicCount = 0;
		Recent.removeListeners();
		socket.on('event:new_topic', onNewTopic);
		socket.on('event:new_post', onNewPost);
	};
 
	function onNewTopic(data) {
		if (ajaxify.data.selectedCategory && parseInt(ajaxify.data.selectedCategory.cid, 10) !== parseInt(data.cid, 10)) {
			return;
		}
 
		if (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'watched') {
			return;
		}
 
		++newTopicCount;
		Recent.updateAlertText();
	}
 
	function onNewPost(data) {
		function showAlert() {
			++newPostCount;
			Recent.updateAlertText();
		}
 
		var post = data.posts[0];
		if (!post || !post.topic) {
			return;
		}
		if (parseInt(post.topic.mainPid, 10) === parseInt(post.pid, 10)) {
			return;
		}
 
		if (ajaxify.data.selectedCategory && parseInt(ajaxify.data.selectedCategory.cid, 10) !== parseInt(post.topic.cid, 10)) {
			return;
		}
 
		if (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'new') {
			return;
		}
 
		if (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'watched') {
			socket.emit('topics.isFollowed', post.tid, function (err, isFollowed) {
				if (err) {
					app.alertError(err.message);
				}
				if (isFollowed) {
					showAlert();
				}
			});
			return;
		}
 
		showAlert();
	}
 
	Recent.removeListeners = function () {
		socket.removeListener('event:new_topic', onNewTopic);
		socket.removeListener('event:new_post', onNewPost);
	};
 
	Recent.updateAlertText = function () {
		var text = '';
 
		if (newTopicCount === 0) {
			if (newPostCount === 1) {
				text = '[[recent:there-is-a-new-post]]';
			} else if (newPostCount > 1) {
				text = '[[recent:there-are-new-posts, ' + newPostCount + ']]';
			}
		} else if (newTopicCount === 1) {
			if (newPostCount === 0) {
				text = '[[recent:there-is-a-new-topic]]';
			} else if (newPostCount === 1) {
				text = '[[recent:there-is-a-new-topic-and-a-new-post]]';
			} else if (newPostCount > 1) {
				text = '[[recent:there-is-a-new-topic-and-new-posts, ' + newPostCount + ']]';
			}
		} else if (newTopicCount > 1) {
			if (newPostCount === 0) {
				text = '[[recent:there-are-new-topics, ' + newTopicCount + ']]';
			} else if (newPostCount === 1) {
				text = '[[recent:there-are-new-topics-and-a-new-post, ' + newTopicCount + ']]';
			} else if (newPostCount > 1) {
				text = '[[recent:there-are-new-topics-and-new-posts, ' + newTopicCount + ', ' + newPostCount + ']]';
			}
		}
 
		text += ' [[recent:click-here-to-reload]]';
 
		$('#new-topics-alert').translateText(text).removeClass('hide').fadeIn('slow');
		$('#category-no-topics').addClass('hide');
	};
 
	Recent.loadMoreTopics = function (direction) {
		if (direction < 0 || !$('[component="category"]').length) {
			return;
		}
 
		infinitescroll.loadMore('topics.loadMoreRecentTopics', {
			after: $('[component="category"]').attr('data-nextstart'),
			cid: utils.params().cid,
			filter: ajaxify.data.selectedFilter.filter,
			set: $('[component="category"]').attr('data-set') ? $('[component="category"]').attr('data-set') : 'topics:recent'
		}, function (data, done) {
			if (data.topics && data.topics.length) {
				Recent.onTopicsLoaded('recent', data.topics, false, done);
			} else {
				done();
			}
			$('[component="category"]').attr('data-nextstart', data.nextStart);
		});
	};
 
	Recent.onTopicsLoaded = function (templateName, topics, showSelect, callback) {
 
		topics = topics.filter(function (topic) {
			return !components.get('category/topic', 'tid', topic.tid).length;
		});
 
		if (!topics.length) {
			return callback();
		}
 
		app.parseAndTranslate(templateName, 'topics', {topics: topics, showSelect: showSelect}, function (html) {
			$('#category-no-topics').remove();
 
			$('[component="category"]').append(html);
			html.find('.timeago').timeago();
			app.createUserTooltips();
			utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
			$(window).trigger('action:topics.loaded', {topics: topics});
			callback();
		});
	};
 
	return Recent;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/register.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/register.js

Statements: 7.26% (9 / 124)      Branches: 0% (0 / 72)      Functions: 0% (0 / 29)      Lines: 7.26% (9 / 124)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243          2                                                                                                     1                                                                                                                   1                                                     1                                                           1                                             1                             1                     1                   1                        
'use strict';
 
/* globals define, app, utils, socket, config, ajaxify, bootbox */
 
 
define('forum/register', ['translator'], function (translator) {
	var Register = {},
		validationError = false,
		successIcon = '';
 
	Register.init = function () {
		var email = $('#email'),
			username = $('#username'),
			password = $('#password'),
			password_confirm = $('#password-confirm'),
			register = $('#register');
 
		handleLanguageOverride();
 
		$('#referrer').val(app.previousUrl);
 
		email.on('blur', function () {
			if (email.val().length) {
				validateEmail(email.val());
			}
		});
 
		var query = utils.params();
		if (query.email && query.token) {
			email.val(decodeURIComponent(query.email));
			$('#token').val(query.token);
		}
 
		// Update the "others can mention you via" text
		username.on('keyup', function () {
			$('#yourUsername').text(this.value.length > 0 ? utils.slugify(this.value) : 'username');
		});
 
		username.on('blur', function () {
			if (username.val().length) {
				validateUsername(username.val());
			}
		});
 
		password.on('blur', function () {
			if (password.val().length) {
				validatePassword(password.val(), password_confirm.val());
			}
		});
 
		password_confirm.on('blur', function () {
			if (password_confirm.val().length) {
				validatePasswordConfirm(password.val(), password_confirm.val());
			}
		});
 
		function validateForm(callback) {
			validationError = false;
			validatePassword(password.val(), password_confirm.val());
			validatePasswordConfirm(password.val(), password_confirm.val());
 
			validateEmail(email.val(), function () {
				validateUsername(username.val(), callback);
			});
		}
 
		register.on('click', function (e) {
			var registerBtn = $(this);
			var errorEl = $('#register-error-notify');
			errorEl.addClass('hidden');
			e.preventDefault();
			validateForm(function () {
				if (validationError) {
					return;
				}
 
				registerBtn.addClass('disabled');
 
				registerBtn.parents('form').ajaxSubmit({
					headers: {
						'x-csrf-token': config.csrf_token
					},
					success: function (data) {
						registerBtn.removeClass('disabled');
						if (!data) {
							return;
						}
						if (data.referrer) {
							window.location.href = data.referrer;
						} else if (data.message) {
							require(['translator'], function (translator) {
								translator.translate(data.message, function (msg) {
									bootbox.alert(msg);
									ajaxify.go('/');
								});
							});
						}
					},
					error: function (data) {
						translator.translate(data.responseText, config.defaultLang, function (translated) {
							if (data.status === 403 && data.responseText === 'Forbidden') {
								window.location.href = config.relative_path + '/register?error=csrf-invalid';
							} else {
								errorEl.find('p').text(translated);
								errorEl.removeClass('hidden');
								registerBtn.removeClass('disabled');
							}
						});
					}
				});
			});
		});
	};
 
	function validateEmail(email, callback) {
		callback = callback || function () {};
		var email_notify = $('#email-notify');
 
		if (!utils.isEmailValid(email)) {
			showError(email_notify, '[[error:invalid-email]]');
			return callback();
		}
 
		socket.emit('user.emailExists', {
			email: email
		}, function (err, exists) {
			if (err) {
				app.alertError(err.message);
				return callback();
			}
 
			if (exists) {
				showError(email_notify, '[[error:email-taken]]');
			} else {
				showSuccess(email_notify, successIcon);
			}
 
			callback();
		});
	}
 
	function validateUsername(username, callback) {
		callback = callback || function () {};
 
		var username_notify = $('#username-notify');
 
		if (username.length < ajaxify.data.minimumUsernameLength) {
			showError(username_notify, '[[error:username-too-short]]');
		} else if (username.length > ajaxify.data.maximumUsernameLength) {
			showError(username_notify, '[[error:username-too-long]]');
		} else if (!utils.isUserNameValid(username) || !utils.slugify(username)) {
			showError(username_notify, '[[error:invalid-username]]');
		} else {
			socket.emit('user.exists', {
				username: username
			}, function (err, exists) {
				if (err) {
					return app.alertError(err.message);
				}
 
				if (exists) {
					showError(username_notify, '[[error:username-taken]]');
				} else {
					showSuccess(username_notify, successIcon);
				}
 
				callback();
			});
		}
	}
 
	function validatePassword(password, password_confirm) {
		var password_notify = $('#password-notify'),
			password_confirm_notify = $('#password-confirm-notify');
 
		if (password.length < ajaxify.data.minimumPasswordLength) {
			showError(password_notify, '[[user:change_password_error_length]]');
		} else if (password.length > 4096) {
			showError(password_notify, '[[error:password-too-long]]');
		} else if (!utils.isPasswordValid(password)) {
			showError(password_notify, '[[user:change_password_error]]');
		} else if (password === $('#username').val()) {
			showError(password_notify, '[[user:password_same_as_username]]');
		} else if (password === $('#email').val()) {
			showError(password_notify, '[[user:password_same_as_email]]');
		} else {
			showSuccess(password_notify, successIcon);
		}
 
		if (password !== password_confirm && password_confirm !== '') {
			showError(password_confirm_notify, '[[user:change_password_error_match]]');
		}
	}
 
	function validatePasswordConfirm(password, password_confirm) {
		var password_notify = $('#password-notify'),
			password_confirm_notify = $('#password-confirm-notify');
 
		if (!password || password_notify.hasClass('alert-error')) {
			return;
		}
 
		if (password !== password_confirm) {
			showError(password_confirm_notify, '[[user:change_password_error_match]]');
		} else {
			showSuccess(password_confirm_notify, successIcon);
		}
	}
 
	function showError(element, msg) {
		translator.translate(msg, function (msg) {
			element.html(msg);
			element.parent()
				.removeClass('register-success')
				.addClass('register-danger');
			element.show();
		});
		validationError = true;
	}
 
	function showSuccess(element, msg) {
		translator.translate(msg, function (msg) {
			element.html(msg);
			element.parent()
				.removeClass('register-danger')
				.addClass('register-success');
			element.show();
		});
	}
 
	function handleLanguageOverride() {
		if (!app.user.uid && config.defaultLang !== config.userLang) {
			var formEl = $('[component="register/local"]'),
				langEl = $('<input type="hidden" name="userLang" value="' + config.userLang + '" />');
 
			formEl.append(langEl);
		}
	}
 
	return Register;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/reset.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/reset.js

Statements: 6.25% (1 / 16)      Branches: 0% (0 / 6)      Functions: 0% (0 / 4)      Lines: 6.25% (1 / 16)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34      2                                                            
"use strict";
/*globals define, app, socket*/
 
define('forum/reset', function () {
	var	ResetPassword = {};
 
	ResetPassword.init = function () {
		var inputEl = $('#email'),
			errorEl = $('#error'),
			successEl = $('#success');
 
		$('#reset').on('click', function () {
			if (inputEl.val() && inputEl.val().indexOf('@') !== -1) {
				socket.emit('user.reset.send', inputEl.val(), function (err) {
					if(err) {
						return app.alertError(err.message);
					}
 
					errorEl.addClass('hide');
					successEl.removeClass('hide');
					inputEl.val('');
				});
			} else {
				successEl.addClass('hide');
				errorEl.removeClass('hide');
			}
			return false;
		});
	};
 
	return ResetPassword;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/reset_code.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/reset_code.js

Statements: 5% (1 / 20)      Branches: 0% (0 / 6)      Functions: 0% (0 / 4)      Lines: 5% (1 / 20)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40      2                                                                        
"use strict";
/*globals define, app, ajaxify, socket, config*/
 
define('forum/reset_code', function () {
	var	ResetCode = {};
 
	ResetCode.init = function () {
		var reset_code = ajaxify.data.code;
 
		var resetEl = $('#reset');
		var password = $('#password');
		var repeat = $('#repeat');
 
		resetEl.on('click', function () {
			if (password.val().length < ajaxify.data.minimumPasswordLength) {
				app.alertError('[[reset_password:password_too_short]]');
			} else if (password.val() !== repeat.val()) {
				app.alertError('[[reset_password:passwords_do_not_match]]');
			} else {
				resetEl.prop('disabled', true).html('<i class="fa fa-spin fa-refresh"></i> Changing Password');
				socket.emit('user.reset.commit', {
					code: reset_code,
					password: password.val()
				}, function (err) {
					if (err) {
						ajaxify.refresh();
						return app.alertError(err.message);
					}
 
					window.location.href = config.relative_path + '/login';
				});
			}
			return false;
		});
	};
 
	return ResetCode;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/search.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/search.js

Statements: 7.37% (7 / 95)      Branches: 0% (0 / 31)      Functions: 0% (0 / 15)      Lines: 7.37% (7 / 95)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165        2                                                             1                                           1         1                                                                                                       1                                                   1                                 1              
'use strict';
 
/* globals app, define, utils*/
 
define('forum/search', ['search', 'autocomplete'], function (searchModule, autocomplete) {
	var	Search = {};
 
	Search.init = function () {
		var searchQuery = $('#results').attr('data-search-query');
 
		$('#search-input').val(searchQuery);
 
		var searchIn = $('#search-in');
 
		fillOutForm();
 
		searchIn.on('change', function () {
			updateFormItemVisiblity(searchIn.val());
		});
 
		highlightMatches(searchQuery);
 
		$('#advanced-search').off('submit').on('submit', function (e) {
			e.preventDefault();
			searchModule.query(getSearchData(), function () {
				$('#search-input').val('');
			});
			return false;
		});
 
		handleSavePreferences();
 
		enableAutoComplete();
	};
 
	function getSearchData() {
		var form = $('#advanced-search');
		var searchData = {
			in: $('#search-in').val()
		};
		searchData.term = $('#search-input').val();
		if (searchData.in === 'posts' || searchData.in === 'titlesposts' || searchData.in === 'titles') {
			searchData.by = form.find('#posted-by-user').val();
			searchData.categories = form.find('#posted-in-categories').val();
			searchData.searchChildren = form.find('#search-children').is(':checked');
			searchData.replies = form.find('#reply-count').val();
			searchData.repliesFilter = form.find('#reply-count-filter').val();
			searchData.timeFilter = form.find('#post-time-filter').val();
			searchData.timeRange = form.find('#post-time-range').val();
			searchData.sortBy = form.find('#post-sort-by').val();
			searchData.sortDirection = form.find('#post-sort-direction').val();
			searchData.showAs = form.find('#show-as-topics').is(':checked') ? 'topics' : 'posts';
		}
 
		return searchData;
	}
 
	function updateFormItemVisiblity(searchIn) {
		var hide = searchIn.indexOf('posts') === -1 && searchIn.indexOf('titles') === -1;
		$('.post-search-item').toggleClass('hide', hide);
	}
 
	function fillOutForm() {
		var params = utils.params();
		var searchData = searchModule.getSearchPreferences();
		var formData = utils.merge(searchData, params);
 
		if (formData) {
			if (params.term) {
				$('#search-input').val(params.term);
			}
 
			if (formData.in) {
				$('#search-in').val(formData.in);
				updateFormItemVisiblity(formData.in);
			}
 
			if (formData.by) {
				$('#posted-by-user').val(formData.by);
			}
 
 
			if (formData.categories) {
				$('#posted-in-categories').val(formData.categories);
			}
 
			if (formData.searchChildren) {
				$('#search-children').prop('checked', true);
			}
 
			if (formData.replies) {
				$('#reply-count').val(formData.replies);
				$('#reply-count-filter').val(formData.repliesFilter);
			}
 
			if (formData.timeRange) {
				$('#post-time-range').val(formData.timeRange);
				$('#post-time-filter').val(formData.timeFilter);
			}
 
			if (formData.sortBy) {
				$('#post-sort-by').val(formData.sortBy);
				$('#post-sort-direction').val(formData.sortDirection);
			}
 
			if (formData.showAs) {
				var isTopic = formData.showAs === 'topics';
				var isPost = formData.showAs === 'posts';
				$('#show-as-topics').prop('checked', isTopic).parent().toggleClass('active', isTopic);
				$('#show-as-posts').prop('checked', isPost).parent().toggleClass('active', isPost);
			}
		}
	}
 
	function highlightMatches(searchQuery) {
		if (!searchQuery) {
			return;
		}
 
		var regexStr = searchQuery.replace(/^"/, '').replace(/"$/, '').trim().split(' ').join('|');
		var regex = new RegExp('(' + regexStr + ')', 'gi');
 
		$('.search-result-text p, .search-result-text h4').each(function () {
			var result = $(this), nested = [];
 
			result.find('*').each(function () {
				$(this).after('<!-- ' + nested.length + ' -->');
				nested.push($('<div />').append($(this)));
			});
 
			result.html(result.html().replace(regex, '<strong>$1</strong>'));
 
			for (var i = 0, ii = nested.length; i < ii; i++) {
				result.html(result.html().replace('<!-- ' + i + ' -->', nested[i].html()));
			}
		});
 
		$('.search-result-text').find('img:not(.not-responsive)').addClass('img-responsive');
	}
 
	function handleSavePreferences() {
		$('#save-preferences').on('click', function () {
			localStorage.setItem('search-preferences', JSON.stringify(getSearchData()));
			app.alertSuccess('[[search:search-preferences-saved]]');
			return false;
		});
 
		$('#clear-preferences').on('click', function () {
			localStorage.removeItem('search-preferences');
			var query = $('#search-input').val();
			$('#advanced-search')[0].reset();
			$('#search-input').val(query);
			app.alertSuccess('[[search:search-preferences-cleared]]');
			return false;
		});
	}
 
	function enableAutoComplete() {
		autocomplete.user($('#posted-by-user'));
	}
 
	return Search;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/tag.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/tag.js

Statements: 10% (2 / 20)      Branches: 0% (0 / 14)      Functions: 0% (0 / 5)      Lines: 10% (2 / 20)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46        2                                   1                                              
'use strict';
 
/* globals define, app, ajaxify, socket */
 
define('forum/tag', ['forum/recent', 'forum/infinitescroll'], function (recent, infinitescroll) {
	var Tag = {};
 
	Tag.init = function () {
		app.enterRoom('tags');
 
		if ($('body').height() <= $(window).height() && $('[component="category"]').children().length >= 20) {
			$('#load-more-btn').show();
		}
 
		$('#load-more-btn').on('click', function () {
			loadMoreTopics();
		});
 
		if (!config.usePagination) {
			infinitescroll.init(loadMoreTopics);
		}
 
		function loadMoreTopics(direction) {
			if(direction < 0 || !$('[component="category"]').length) {
				return;
			}
 
			infinitescroll.loadMore('topics.loadMoreFromSet', {
				set: 'tag:' + ajaxify.data.tag + ':topics',
				after: $('[component="category"]').attr('data-nextstart')
			}, function (data, done) {
				if (data.topics && data.topics.length) {
					recent.onTopicsLoaded('tag', data.topics, false, done);
				} else {
					done();
					$('#load-more-btn').hide();
				}
				$('[component="category"]').attr('data-nextstart', data.nextStart);
			});
		}
	};
 
	return Tag;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/tags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/tags.js

Statements: 7.89% (3 / 38)      Branches: 0% (0 / 22)      Functions: 0% (0 / 13)      Lines: 7.89% (3 / 38)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77        2                                                                                                 1                     1                        
'use strict';
 
/* globals define, app, utils, socket */
 
define('forum/tags', ['forum/infinitescroll'], function (infinitescroll) {
	var Tags = {};
	var timeoutId = 0;
 
	Tags.init = function () {
		app.enterRoom('tags');
 
		$('#tag-search').on('input propertychange', function () {
			if (timeoutId) {
				clearTimeout(timeoutId);
				timeoutId = 0;
			}
 
			if (!$('#tag-search').val().length) {
				return resetSearch();
			}
 
			timeoutId = setTimeout(function () {
				socket.emit('topics.searchAndLoadTags', {query: $('#tag-search').val()}, function (err, results) {
					if (err) {
						return app.alertError(err.message);
					}
					onTagsLoaded(results.tags, true, function () {
						timeoutId = 0;
					});
				});
			}, 100);
		});
 
		infinitescroll.init(Tags.loadMoreTags);
	};
 
	Tags.loadMoreTags = function (direction) {
		if (direction < 0 || !$('.tag-list').length || $('#tag-search').val()) {
			return;
		}
 
		infinitescroll.loadMore('topics.loadMoreTags', {
			after: $('.tag-list').attr('data-nextstart')
		}, function (data, done) {
			if (data && data.tags && data.tags.length) {
				onTagsLoaded(data.tags, false, done);
				$('.tag-list').attr('data-nextstart', data.nextStart);
			} else {
				done();
			}
		});
	};
 
	function resetSearch() {
		socket.emit('topics.loadMoreTags', {
			after: 0
		}, function (err, data) {
			if (err) {
				return app.alertError(err.message);
			}
			onTagsLoaded(data.tags, true);
		});
	}
 
	function onTagsLoaded(tags, replace, callback) {
		callback = callback || function () {};
		app.parseAndTranslate('tags', 'tags', {tags: tags}, function (html) {
			$('.tag-list')[replace ? 'html' : 'append'](html);
			utils.makeNumbersHumanReadable(html.find('.human-readable-number'));
			callback();
		});
	}
 
	return Tags;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/topic.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/topic.js

Statements: 6.41% (10 / 156)      Branches: 0% (0 / 111)      Functions: 0% (0 / 29)      Lines: 6.41% (10 / 156)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308          2                                                                                                                                                     1           1                             1                                                                                   1                                                             1                               1                   1                         1                                                                                                                             1                                                              
'use strict';
 
 
/* globals define, app, socket, config, ajaxify, RELATIVE_PATH, utils */
 
define('forum/topic', [
	'forum/infinitescroll',
	'forum/topic/threadTools',
	'forum/topic/postTools',
	'forum/topic/events',
	'forum/topic/posts',
	'forum/topic/replies',
	'navigator',
	'sort',
	'components'
], function (infinitescroll, threadTools, postTools, events, posts, replies, navigator, sort, components) {
	var	Topic = {},
		currentUrl = '';
 
	$(window).on('action:ajaxify.start', function (ev, data) {
		if (Topic.replaceURLTimeout) {
			clearTimeout(Topic.replaceURLTimeout);
			Topic.replaceURLTimeout = 0;
		}
 
		if (ajaxify.currentPage !== data.url) {
			navigator.disable();
			components.get('navbar/title').find('span').text('').hide();
			app.removeAlert('bookmark');
 
			events.removeListeners();
			$(window).off('keydown', onKeyDown);
		}
 
		if (data.url && !data.url.startsWith('topic/')) {
			require(['search'], function (search) {
				if (search.topicDOM.active) {
					search.topicDOM.end();
				}
			});
		}
	});
 
	Topic.init = function () {
		var tid = ajaxify.data.tid;
 
		$(window).trigger('action:topic.loading');
 
		app.enterRoom('topic_' + tid);
 
		posts.processPage(components.get('post'));
 
		postTools.init(tid);
		threadTools.init(tid);
		replies.init(tid);
		events.init();
 
		sort.handleSort('topicPostSort', 'user.setTopicSort', 'topic/' + ajaxify.data.slug);
 
		if (!config.usePagination) {
			infinitescroll.init($('[component="topic"]'), posts.loadMorePosts);
		}
 
		addBlockQuoteHandler();
 
		addParentHandler();
 
		handleKeys();
 
		navigator.init('[component="post"]', ajaxify.data.postcount, Topic.toTop, Topic.toBottom, Topic.navigatorCallback, Topic.calculateIndex);
 
		handleBookmark(tid);
 
		$(window).on('scroll', updateTopicTitle);
 
		handleTopicSearch();
 
		$(window).trigger('action:topic.loaded', ajaxify.data);
	};
 
	function handleKeys() {
		if (!config.usePagination) {
			$(window).off('keydown', onKeyDown).on('keydown', onKeyDown);
		}
	}
 
	function onKeyDown(ev) {
		if (ev.target.nodeName === 'BODY') {
			if (ev.shiftKey || ev.ctrlKey || ev.altKey) {
				return;
			}
			if (ev.which === 36) { // home key
				Topic.toTop();
				return false;
			} else if (ev.which === 35) { // end key
				Topic.toBottom();
				return false;
			}
		}
	}
 
	function handleTopicSearch() {
		require(['search', 'mousetrap'], function (search, mousetrap) {
			$('.topic-search')
				.on('click', '.prev', function () {
					search.topicDOM.prev();
				})
				.on('click', '.next', function () {
					search.topicDOM.next();
				});
 
			mousetrap.bind('ctrl+f', function (e) {
				if (config.topicSearchEnabled) {
					// If in topic, open search window and populate, otherwise regular behaviour
					var match = ajaxify.currentPage.match(/^topic\/([\d]+)/),
						tid;
					if (match) {
						e.preventDefault();
						tid = match[1];
						$('#search-fields input').val('in:topic-' + tid + ' ');
						app.prepareSearch();
					}
				}
			});
		});
	}
 
	Topic.toTop = function () {
		navigator.scrollTop(0);
	};
 
	Topic.toBottom = function () {
		socket.emit('topics.postcount', ajaxify.data.tid, function (err, postCount) {
			if (err) {
				return app.alertError(err.message);
			}
			if (config.topicPostSort !== 'oldest_to_newest') {
				postCount = 2;
			}
			navigator.scrollBottom(postCount - 1);
		});
	};
 
	function handleBookmark(tid) {
		// use the user's bookmark data if available, fallback to local if available
		var bookmark = ajaxify.data.bookmark || localStorage.getItem('topic:' + tid + ':bookmark');
		var postIndex = getPostIndex();
 
		if (postIndex && window.location.search.indexOf('page=') === -1) {
			if (components.get('post/anchor', postIndex).length) {
				return navigator.scrollToPostIndex(postIndex, true, 0);
			}
		} else if (bookmark && (!config.usePagination || (config.usePagination && ajaxify.data.pagination.currentPage === 1)) && ajaxify.data.postcount > ajaxify.data.bookmarkThreshold) {
			navigator.update(0);
			app.alert({
				alert_id: 'bookmark',
				message: '[[topic:bookmark_instructions]]',
				timeout: 0,
				type: 'info',
				clickfn : function () {
					navigator.scrollToPost(parseInt(bookmark - 1, 10), true);
				},
				closefn : function () {
					localStorage.removeItem('topic:' + tid + ':bookmark');
				}
			});
			setTimeout(function () {
				app.removeAlert('bookmark');
			}, 10000);
		} else {
			navigator.update(0);
		}
	}
 
	function getPostIndex() {
		var parts = window.location.pathname.split('/');
		var lastPart = parts[parts.length - 1];
		if (lastPart && utils.isNumber(lastPart)) {
			lastPart = Math.max(0, parseInt(lastPart, 10) - 1);
		} else {
			return 0;
		}
 
		if (lastPart > 0 && !components.get('post/anchor', lastPart).length) {
			return components.get('post/anchor').last().attr('name');
		}
 
		return lastPart;
	}
 
	function addBlockQuoteHandler() {
		components.get('topic').on('click', 'blockquote .toggle', function () {
			var blockQuote = $(this).parent('blockquote');
			var toggle = $(this);
			blockQuote.toggleClass('uncollapsed');
			var collapsed = !blockQuote.hasClass('uncollapsed');
			toggle.toggleClass('fa-angle-down', collapsed).toggleClass('fa-angle-up', !collapsed);
		});
	}
 
	function addParentHandler() {
		components.get('topic').on('click', '[component="post/parent"]', function (e) {
			var toPid = $(this).attr('data-topid');
 
			var toPost = $('[component="post"][data-pid="' + toPid + '"]');
			if (toPost.length) {
				e.preventDefault();
				navigator.scrollToPost(toPost.attr('data-index'), true);
				return false;
			}
		});
	}
 
	function updateTopicTitle() {
		var span = components.get('navbar/title').find('span');
		if ($(window).scrollTop() > 50 && span.hasClass('hidden')) {
			span.html(ajaxify.data.title).removeClass('hidden');
		} else if ($(window).scrollTop() <= 50 && !span.hasClass('hidden')) {
			span.html('').addClass('hidden');
		}
		if ($(window).scrollTop() > 300) {
			app.removeAlert('bookmark');
		}
	}
 
	Topic.calculateIndex = function (index, elementCount) {
		if (index !== 1 && config.topicPostSort !== 'oldest_to_newest') {
			return elementCount - index + 2;
		}
		return index;
	};
 
	Topic.navigatorCallback = function (index, elementCount, threshold) {
		var path = ajaxify.removeRelativePath(window.location.pathname.slice(1));
		if (!path.startsWith('topic')) {
			return;
		}
 
		if (navigator.scrollActive) {
			return;
		}
 
		posts.loadImages(threshold);
 
		var newUrl = 'topic/' + ajaxify.data.slug + (index > 1 ? ('/' + index) : '');
 
		if (newUrl !== currentUrl) {
 
			if (Topic.replaceURLTimeout) {
				clearTimeout(Topic.replaceURLTimeout);
			}
 
			Topic.replaceURLTimeout = setTimeout(function () {
 
				if (index >= elementCount && app.user.uid) {
					socket.emit('topics.markAsRead', [ajaxify.data.tid]);
				}
 
				updateUserBookmark(index);
 
				Topic.replaceURLTimeout = 0;
				if (history.replaceState) {
					var search = window.location.search || '';
					if (!config.usePagination) {
						search = (search && !/^\?page=\d+$/.test(search) ? search : '');
					}
 
					history.replaceState({
						url: newUrl + search
					}, null, window.location.protocol + '//' + window.location.host + RELATIVE_PATH + '/' + newUrl + search);
				}
				currentUrl = newUrl;
			}, 500);
		}
	};
 
	function updateUserBookmark(index) {
		var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark';
		var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey);
 
		if (ajaxify.data.postcount > ajaxify.data.bookmarkThreshold && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) {
			if (app.user.uid) {
				socket.emit('topics.bookmark', {
					'tid': ajaxify.data.tid,
					'index': index
				}, function (err) {
					if (err) {
						return app.alertError(err.message);
					}
					ajaxify.data.bookmark = index;
				});
			} else {
				localStorage.setItem(bookmarkKey, index);
			}
		}
 
		// removes the bookmark alert when we get to / past the bookmark
		if (!currentBookmark || parseInt(index, 10) >= parseInt(currentBookmark, 10)) {
			app.removeAlert('bookmark');
		}
 
	}
 
 
	return Topic;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/unread.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/unread.js

Statements: 7.35% (5 / 68)      Branches: 0% (0 / 26)      Functions: 0% (0 / 17)      Lines: 7.35% (5 / 68)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131        2                                                                                                   1                                                                 1                                           1                     1                    
'use strict';
 
/* globals define, app, socket */
 
define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll', 'components'], function (recent, topicSelect, infinitescroll, components) {
	var Unread = {};
 
	$(window).on('action:ajaxify.start', function (ev, data) {
		if (ajaxify.currentPage !== data.url) {
			recent.removeListeners();
		}
	});
 
	Unread.init = function () {
		app.enterRoom('unread_topics');
 
		$('#new-topics-alert').on('click', function () {
			$(this).addClass('hide');
		});
 
		recent.watchForNewPosts();
 
		$(window).trigger('action:topics.loaded', {topics: ajaxify.data.topics});
 
		$('#markSelectedRead').on('click', function () {
			var tids = topicSelect.getSelectedTids();
			if(!tids.length) {
				return;
			}
			socket.emit('topics.markAsRead', tids, function (err) {
				if(err) {
					return app.alertError(err.message);
				}
 
				doneRemovingTids(tids);
			});
		});
 
		$('#markAllRead').on('click', function () {
			socket.emit('topics.markAllRead', function (err) {
				if(err) {
					return app.alertError(err.message);
				}
 
				app.alertSuccess('[[unread:topics_marked_as_read.success]]');
 
				$('[component="category"]').empty();
				$('[component="pagination"]').addClass('hidden');
				$('#category-no-topics').removeClass('hidden');
				$('.markread').addClass('hidden');
			});
		});
 
		$('.markread').on('click', '.category', function () {
			function getCategoryTids(cid) {
				var tids = [];
				components.get('category/topic', 'cid', cid).each(function () {
					tids.push($(this).attr('data-tid'));
				});
				return tids;
			}
			var cid = $(this).attr('data-cid');
			var tids = getCategoryTids(cid);
 
			socket.emit('topics.markCategoryTopicsRead', cid, function (err) {
				if(err) {
					return app.alertError(err.message);
				}
 
				doneRemovingTids(tids);
			});
		});
 
		topicSelect.init();
 
		if ($("body").height() <= $(window).height() && $('[component="category"]').children().length >= 20) {
			$('#load-more-btn').show();
		}
 
		$('#load-more-btn').on('click', function () {
			loadMoreTopics();
		});
 
		if (!config.usePagination) {
			infinitescroll.init(loadMoreTopics);
		}
 
		function loadMoreTopics(direction) {
			if(direction < 0 || !$('[component="category"]').length) {
				return;
			}
			var params = utils.params();
			var cid = params.cid;
			infinitescroll.loadMore('topics.loadMoreUnreadTopics', {
				after: $('[component="category"]').attr('data-nextstart'),
				cid: cid,
				filter: ajaxify.data.selectedFilter.filter
			}, function (data, done) {
				if (data.topics && data.topics.length) {
					recent.onTopicsLoaded('unread', data.topics, true, done);
					$('[component="category"]').attr('data-nextstart', data.nextStart);
				} else {
					done();
					$('#load-more-btn').hide();
				}
			});
		}
	};
 
	function doneRemovingTids(tids) {
		removeTids(tids);
 
		app.alertSuccess('[[unread:topics_marked_as_read.success]]');
 
		if (!$('[component="category"]').children().length) {
			$('#category-no-topics').removeClass('hidden');
			$('.markread').addClass('hidden');
		}
	}
 
	function removeTids(tids) {
		for(var i = 0; i < tids.length; ++i) {
			components.get('category/topic', 'tid', tids[i]).remove();
		}
	}
 
 
	return Unread;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/users.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/client/users.js

Statements: 12.2% (10 / 82)      Branches: 0% (0 / 37)      Functions: 0% (0 / 21)      Lines: 12.2% (10 / 82)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152        2                                                   1                                 1                                                           1                           1                 1                             1               1       1       1                                        
'use strict';
 
/* globals define, socket, app, templates, bootbox, utils */
 
define('forum/users', ['translator'], function (translator) {
	var	Users = {};
 
	var searchTimeoutID = 0;
 
	$(window).on('action:ajaxify.start', function () {
		if (searchTimeoutID) {
			clearTimeout(searchTimeoutID);
			searchTimeoutID = 0;
		}
	});
 
	Users.init = function () {
		app.enterRoom('user_list');
 
		var section = utils.params().section ? ('?section=' + utils.params().section) : '';
		$('.nav-pills li').removeClass('active').find('a[href="' + window.location.pathname + section + '"]').parent().addClass('active');
 
		handleSearch();
 
		handleInvite();
 
		socket.removeListener('event:user_status_change', onUserStatusChange);
		socket.on('event:user_status_change', onUserStatusChange);
	};
 
	function handleSearch() {
		searchTimeoutID = 0;
 
		$('#search-user').on('keyup', function () {
			if (searchTimeoutID) {
				clearTimeout(searchTimeoutID);
				searchTimeoutID = 0;
			}
 
			searchTimeoutID = setTimeout(doSearch, 150);
		});
 
		$('.search select, .search input[type="checkbox"]').on('change', function () {
			doSearch();
		});
	}
 
	function doSearch() {
		$('[component="user/search/icon"]').removeClass('fa-search').addClass('fa-spinner fa-spin');
		var username = $('#search-user').val();
		var activeSection = getActiveSection();
 
		var query = {
			section: activeSection,
			page: 1
		};
 
		if (!username) {
			return loadPage(query);
		}
 
		query.term = username;
		query.sortBy = getSortBy();
 
		if ($('.search .online-only').is(':checked') || (activeSection === 'online')) {
			query.onlineOnly = true;
		}
		if (activeSection === 'banned') {
			query.bannedOnly =  true;
		}
		if (activeSection === 'flagged') {
			query.flaggedOnly = true;
		}
 
		loadPage(query);
	}
 
	function getSortBy() {
		var sortBy;
		var activeSection = getActiveSection();
		if (activeSection === 'sort-posts') {
			sortBy = 'postcount';
		} else if (activeSection === 'sort-reputation') {
			sortBy = 'reputation';
		} else if (activeSection === 'users') {
			sortBy = 'joindate';
		}
		return sortBy;
	}
 
 
	function loadPage(query) {
		var qs = decodeURIComponent($.param(query));
		$.get(config.relative_path + '/api/users?' + qs, renderSearchResults).fail(function (xhrErr) {
			if (xhrErr && xhrErr.responseJSON && xhrErr.responseJSON.error) {
				app.alertError(xhrErr.responseJSON.error);
			}
		});
	}
 
	function renderSearchResults(data) {
		templates.parse('partials/paginator', {pagination: data.pagination}, function (html) {
			$('.pagination-container').replaceWith(html);
		});
 
		templates.parse('users', 'users', data, function (html) {
			translator.translate(html, function (translated) {
				translated = $(translated);
				$('#users-container').html(translated);
				translated.find('span.timeago').timeago();
				$('[component="user/search/icon"]').addClass('fa-search').removeClass('fa-spinner fa-spin');
			});
		});
	}
 
	function onUserStatusChange(data) {
		var section = getActiveSection();
 
		if ((section.startsWith('online') || section.startsWith('users'))) {
			updateUser(data);
		}
	}
 
	function updateUser(data) {
		app.updateUserStatus($('#users-container [data-uid="' + data.uid + '"] [component="user/status"]'), data.status);
	}
 
	function getActiveSection() {
		return utils.params().section || '';
	}
 
	function handleInvite() {
		$('[component="user/invite"]').on('click', function () {
			bootbox.prompt('Email: ', function (email) {
				if (!email) {
					return;
				}
 
				socket.emit('user.invite', email, function (err) {
					if (err) {
						return app.alertError(err.message);
					}
					app.alertSuccess('[[users:invitation-email-sent, ' + email + ']]');
				});
			});
		});
	}
 
	return Users;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/

Statements: 5.77% (97 / 1681)      Branches: 1.55% (14 / 903)      Functions: 1.45% (6 / 414)      Lines: 4.6% (68 / 1478)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/public/src/modules/
File Statements Branches Functions Lines
alerts.js 8.47% (5 / 59) 0% (0 / 28) 0% (0 / 15) 8.47% (5 / 59)
autocomplete.js 3.7% (1 / 27) 0% (0 / 16) 0% (0 / 12) 3.7% (1 / 27)
chat.js 1.04% (2 / 193) 0% (0 / 53) 0% (0 / 44) 1.04% (2 / 193)
components.js 4% (1 / 25) 0% (0 / 6) 0% (0 / 16) 4% (1 / 25)
coverPhoto.js 4.88% (2 / 41) 0% (0 / 8) 0% (0 / 9) 4.88% (2 / 41)
iconSelect.js 3.92% (2 / 51) 0% (0 / 22) 0% (0 / 12) 3.92% (2 / 51)
navigator.js 2.91% (5 / 172) 0% (0 / 83) 0% (0 / 28) 2.91% (5 / 172)
notifications.js 2.11% (2 / 95) 0% (0 / 46) 0% (0 / 20) 2.11% (2 / 95)
postSelect.js 13.33% (4 / 30) 0% (0 / 8) 0% (0 / 8) 13.79% (4 / 29)
scrollStop.js 10% (1 / 10) 0% (0 / 6) 0% (0 / 3) 10% (1 / 10)
search.js 2.3% (2 / 87) 0% (0 / 50) 0% (0 / 18) 2.3% (2 / 87)
settings.js 0.97% (2 / 207) 0% (0 / 174) 0% (0 / 38) 0.97% (2 / 207)
share.js 14.29% (4 / 28) 0% (0 / 2) 0% (0 / 11) 14.29% (4 / 28)
sort.js 6.67% (1 / 15) 0% (0 / 2) 0% (0 / 4) 6.67% (1 / 15)
sounds.js 14.58% (7 / 48) 0% (0 / 22) 0% (0 / 13) 14.58% (7 / 48)
string.js 14.93% (30 / 201) 4.57% (9 / 197) 5.33% (4 / 75) 100% (1 / 1)
taskbar.js 5.97% (4 / 67) 0% (0 / 22) 0% (0 / 16) 5.97% (4 / 67)
topicSelect.js 10.2% (5 / 49) 0% (0 / 8) 0% (0 / 11) 10.2% (5 / 49)
translator.js 5.16% (11 / 213) 4.31% (5 / 116) 4.65% (2 / 43) 5.21% (11 / 211)
uploader.js 9.52% (6 / 63) 0% (0 / 34) 0% (0 / 18) 9.52% (6 / 63)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/alerts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/alerts.js

Statements: 8.47% (5 / 59)      Branches: 0% (0 / 28)      Functions: 0% (0 / 15)      Lines: 8.47% (5 / 59)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110      2                                 1                                                                                   1                                                           1           1                      
'use strict';
/* globals define, templates */
 
define('alerts', ['translator', 'components'], function (translator, components) {
	var module = {};
 
	module.alert = function (params) {
		params.alert_id = 'alert_button_' + (params.alert_id ? params.alert_id : new Date().getTime());
		params.title = params.title ? params.title.trim() || '' : '';
		params.message = params.message ? params.message.trim() : '';
		params.type = params.type || 'info';
 
		var alert = $('#' + params.alert_id);
		if (alert.length) {
			updateAlert(alert, params);
		} else {
			createNew(params);
		}
	};
 
	function createNew(params) {
		templates.parse('alert', params, function (alertTpl) {
			translator.translate(alertTpl, function (translatedHTML) {
				var alert = $('#' + params.alert_id);
				if (alert.length) {
					return updateAlert(alert, params);
				}
				alert = $(translatedHTML);
				alert.fadeIn(200);
 
				components.get('toaster/tray').prepend(alert);
 
				if(typeof params.closefn === 'function') {
					alert.find('button').on('click', function () {
						params.closefn();
						fadeOut(alert);
						return false;
					});
				}
 
				if (params.timeout) {
					startTimeout(alert, params.timeout);
				}
 
				if (typeof params.clickfn === 'function') {
					alert
						.addClass('pointer')
						.on('click', function (e) {
							if(!$(e.target).is('.close')) {
								params.clickfn();
							}
							fadeOut(alert);
						});
				}
			});
		});
	}
 
	module.remove = function (id) {
		$('#alert_button_' + id).remove();
	};
 
	function updateAlert(alert, params) {
		alert.find('strong').html(params.title);
		alert.find('p').html(params.message);
		alert.attr('class', 'alert alert-dismissable alert-' + params.type);
 
		clearTimeout(parseInt(alert.attr('timeoutId'), 10));
		if (params.timeout) {
			startTimeout(alert, params.timeout);
		}
 
		alert.children().fadeOut(100);
		translator.translate(alert.html(), function (translatedHTML) {
			alert.children().fadeIn(100);
			alert.html(translatedHTML);
		});
 
		// Handle changes in the clickfn
		alert.off('click').removeClass('pointer');
		if (typeof params.clickfn === 'function') {
			alert
				.addClass('pointer')
				.on('click', function (e) {
					if(!$(e.target).is('.close')) {
						params.clickfn();
					}
					fadeOut(alert);
				});
		}
	}
 
	function fadeOut(alert) {
		alert.fadeOut(500, function () {
			$(this).remove();
		});
	}
 
	function startTimeout(alert, timeout) {
		var timeoutId = setTimeout(function () {
			fadeOut(alert);
		}, timeout);
 
		alert.attr('timeoutId', timeoutId);
	}
 
	return module;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/autocomplete.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/autocomplete.js

Statements: 3.7% (1 / 27)      Branches: 0% (0 / 16)      Functions: 0% (0 / 12)      Lines: 3.7% (1 / 27)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81          2                                                                                                                                                      
 
'use strict';
 
/* globals define, socket, app */
 
define('autocomplete', function () {
	var module = {};
 
	module.user = function (input, onselect) {
		app.loadJQueryUI(function () {
			input.autocomplete({
				delay: 200,
				open: function () {
					$(this).autocomplete('widget').css('z-index', 20000);
				},
				select: onselect,
				source: function (request, response) {
					socket.emit('user.search', {query: request.term}, function (err, result) {
						if (err) {
							return app.alertError(err.message);
						}
 
						if (result && result.users) {
							var names = result.users.map(function (user) {
								var username = $('<div/>').html(user.username).text();
								return user && {
									label: username,
									value: username,
									user: {
										uid: user.uid,
										name: user.username,
										slug: user.userslug
									}
								};
							});
							response(names);
						}
						$('.ui-autocomplete a').attr('data-ajaxify', 'false');
					});
				}
			});
		});
	};
 
	module.group = function (input, onselect) {
		app.loadJQueryUI(function () {
			input.autocomplete({
				delay: 200,
				select: onselect,
				source: function (request, response) {
					socket.emit('groups.search', {
						query: request.term
					}, function (err, results) {
						if (err) {
							return app.alertError(err.message);
						}
 
						if (results && results.length) {
							var names = results.map(function (group) {
								return group && {
									label: group.name,
									value: group.name,
									group: {
										name: group.name,
										slug: group.slug
									}
								};
							});
							response(names);
						}
						$('.ui-autocomplete a').attr('data-ajaxify', 'false');
					});
				}
			});
		});
	};
 
	return module;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/chat.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/chat.js

Statements: 1.04% (2 / 193)      Branches: 0% (0 / 53)      Functions: 0% (0 / 44)      Lines: 1.04% (2 / 193)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359      2                                                                                                                                                                                                                                                                                                                                                                                                               1                                                                                                                                                                                                                                                                                                                      
"use strict";
/* globals app, define, socket, templates, utils, ajaxify */
 
define('chat', [
	'components',
	'taskbar',
	'string',
	'sounds',
	'forum/chats',
	'forum/chats/messages',
	'translator'
], function (components, taskbar, S, sounds, Chats, ChatsMessages, translator) {
 
	var module = {};
	var newMessage = false;
 
	module.prepareDOM = function () {
		var chatsToggleEl = components.get('chat/dropdown');
		var chatsListEl = components.get('chat/list');
 
		chatsToggleEl.on('click', function () {
			if (chatsToggleEl.parent().hasClass('open')) {
				return;
			}
 
			module.loadChatsDropdown(chatsListEl);
		});
 
		chatsListEl.on('click', '[data-roomid]', function (ev) {
			if ($(ev.target).parents('.user-link').length) {
				return;
			}
			var roomId = $(this).attr('data-roomid');
			if (!ajaxify.currentPage.match(/^chats\//)) {
				app.openChat(roomId);
			} else {
				ajaxify.go('user/' + app.user.userslug + '/chats/' + roomId);
			}
		});
 
		$('[component="chats/mark-all-read"]').on('click', function () {
			socket.emit('modules.chats.markAllRead', function (err) {
				if (err) {
					return app.alertError(err);
				}
			});
		});
 
		socket.on('event:chats.receive', function (data) {
			var username = data.message.fromUser.username;
			var isSelf = data.self === 1;
			data.message.self = data.self;
 
			newMessage = data.self === 0;
			if (module.modalExists(data.roomId)) {
				var modal = module.getModal(data.roomId);
 
				ChatsMessages.appendChatMessage(modal.find('.chat-content'), data.message);
 
				if (modal.is(':visible')) {
					taskbar.updateActive(modal.attr('UUID'));
					ChatsMessages.scrollToBottom(modal.find('.chat-content'));
				} else {
					module.toggleNew(modal.attr('UUID'), true, true);
				}
 
				if (!isSelf && (!modal.is(':visible') || !app.isFocused)) {
					app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + username + ']]');
					sounds.play('chat-incoming');
 
					taskbar.push('chat', modal.attr('UUID'), {
						title: username,
						touid: data.message.fromUser.uid,
						roomId: data.roomId
					});
				}
			} else {
				socket.emit('modules.chats.loadRoom', {roomId: data.roomId}, function (err, roomData) {
					if (err) {
						return app.alertError(err.message);
					}
					roomData.users = roomData.users.filter(function (user) {
						return user && parseInt(user.uid, 10) !== parseInt(app.user.uid, 10);
					});
					roomData.silent = true;
					roomData.uid = app.user.uid;
					module.createModal(roomData, function (modal) {
						module.toggleNew(modal.attr('UUID'), !isSelf, true);
						if (!isSelf) {
							app.alternatingTitle('[[modules:chat.user_has_messaged_you, ' + username + ']]');
							sounds.play('chat-incoming');
						}
					});
				});
			}
		});
 
		socket.on('event:user_status_change', function (data) {
			var modal = module.getModal(data.uid);
			app.updateUserStatus(modal.find('[component="user/status"]'), data.status);
		});
 
		socket.on('event:chats.roomRename', function (data) {
			module.getModal(data.roomId).find('[component="chat/room/name"]').val($('<div/>').html(data.newName).text());
		});
 
		ChatsMessages.onChatMessageEdit();
	};
 
	module.loadChatsDropdown = function (chatsListEl) {
		socket.emit('modules.chats.getRecentChats', {uid: app.user.uid, after: 0}, function (err, data) {
			if (err) {
				return app.alertError(err.message);
			}
 
			var rooms = data.rooms.filter(function (room) {
			    return room.teaser;
			});
 
			templates.parse('partials/chats/dropdown', {
				rooms: rooms
			}, function (html) {
				translator.translate(html, function (translated) {
					chatsListEl.empty().html(translated);
					app.createUserTooltips(chatsListEl, 'right');
				});
			});
		});
	};
 
	module.bringModalToTop = function (chatModal) {
		var topZ = 0;
 
		taskbar.updateActive(chatModal.attr('UUID'));
 
		if ($('.chat-modal').length === 1) {
			return;
		}
		$('.chat-modal').each(function () {
			var thisZ = parseInt($(this).css('zIndex'), 10);
			if (thisZ > topZ) {
				topZ = thisZ;
			}
		});
 
		chatModal.css('zIndex', topZ + 1);
	};
 
	module.getModal = function (roomId) {
		return $('#chat-modal-' + roomId);
	};
 
	module.modalExists = function (roomId) {
		return $('#chat-modal-' + roomId).length !== 0;
	};
 
	module.createModal = function (data, callback) {
		app.parseAndTranslate('chat', data, function (chatModal) {
 
			var uuid = utils.generateUUID();
			var dragged = false;
 
			chatModal.attr('id', 'chat-modal-' + data.roomId);
			chatModal.attr('roomId', data.roomId);
			chatModal.attr('intervalId', 0);
			chatModal.attr('UUID', uuid);
			chatModal.css('position', 'fixed');
			chatModal.css('zIndex', 100);
			chatModal.appendTo($('body'));
			chatModal.find('.timeago').timeago();
			module.center(chatModal);
 
			app.loadJQueryUI(function () {
				chatModal.find('.modal-content').resizable({
					handles: 'n, e, s, w, se',
					minHeight: 250,
					minWidth: 400
				});
 
				chatModal.find('.modal-content').on('resize', function (event, ui) {
					if (ui.originalSize.height === ui.size.height) {
						return;
					}
 
					chatModal.find('.chat-content').css('height', module.calculateChatListHeight(chatModal));
				});
 
				chatModal.draggable({
					start:function () {
						module.bringModalToTop(chatModal);
					},
					stop:function () {
						chatModal.find('#chat-message-input').focus();
					},
					distance: 10,
					handle: '.modal-header'
				});
			});
 
			chatModal.find('#chat-close-btn').on('click', function () {
				module.close(chatModal);
			});
 
			function gotoChats() {
				var text = components.get('chat/input').val();
				$(window).one('action:ajaxify.end', function () {
					components.get('chat/input').val(text);
				});
 
				ajaxify.go('user/' + app.user.userslug + '/chats/' + chatModal.attr('roomId'));
				module.close(chatModal);
			}
 
			chatModal.find('.modal-header').on('dblclick', gotoChats);
			chatModal.find('button[data-action="maximize"]').on('click', gotoChats);
			chatModal.find('button[data-action="minimize"]').on('click', function () {
				var uuid = chatModal.attr('uuid');
				module.minimize(uuid);
			});
 
			chatModal.on('click', function () {
				module.bringModalToTop(chatModal);
 
				if (dragged) {
					dragged = false;
				}
			});
 
			chatModal.on('mousemove', function (e) {
				if (e.which === 1) {
					dragged = true;
				}
			});
 
			chatModal.on('mousemove keypress click', function () {
				if (newMessage) {
					socket.emit('modules.chats.markRead', data.roomId);
					newMessage = false;
				}
			});
 
			Chats.addEditDeleteHandler(chatModal.find('[component="chat/messages"]'), data.roomId);
 
			chatModal.find('[component="chat/controlsToggle"]').on('click', function () {
				var messagesEl = chatModal.find('[component="chat/messages"]');
 
				chatModal.find('[component="chat/controls"]').toggle();
				messagesEl.css('height', module.calculateChatListHeight(chatModal));
			});
 
			Chats.addRenameHandler(chatModal.attr('roomId'), chatModal.find('[component="chat/room/name"]'));
 
			Chats.addSendHandlers(chatModal.attr('roomId'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn'));
 
			Chats.createTagsInput(chatModal.find('.users-tag-input'), data);
			Chats.createAutoComplete(chatModal.find('[component="chat/input"]'));
 
			Chats.addScrollHandler(chatModal.attr('roomId'), data.uid, chatModal.find('.chat-content'));
 
			taskbar.push('chat', chatModal.attr('UUID'), {
				title: data.roomName || (data.users.length ? data.users[0].username : ''),
				roomId: data.roomId,
				icon: 'fa-comment',
				state: ''
			});
 
			$(window).trigger('action:chat.loaded', chatModal);
 
			if (typeof callback === 'function') {
				callback(chatModal);
			}
		});
	};
 
	module.focusInput = function (chatModal) {
		chatModal.find('#chat-message-input').focus();
	};
 
	module.close = function (chatModal) {
		clearInterval(chatModal.attr('intervalId'));
		chatModal.attr('intervalId', 0);
		chatModal.remove();
		chatModal.data('modal', null);
		taskbar.discard('chat', chatModal.attr('UUID'));
 
		if (chatModal.attr('data-mobile')) {
			module.disableMobileBehaviour(chatModal);
		}
	};
 
	module.center = function (chatModal) {
		var hideAfter = false;
		if (chatModal.hasClass('hide')) {
			chatModal.removeClass('hide');
			hideAfter = true;
		}
		chatModal.css('left', Math.max(0, (($(window).width() - $(chatModal).outerWidth()) / 2) + $(window).scrollLeft()) + 'px');
		chatModal.css('top', Math.max(0, $(window).height() / 2 - $(chatModal).outerHeight() / 2) + 'px');
 
		if (hideAfter) {
			chatModal.addClass('hide');
		}
		return chatModal;
	};
 
	module.load = function (uuid) {
		var chatModal = $('div[UUID="' + uuid + '"]');
		chatModal.removeClass('hide');
		taskbar.updateActive(uuid);
		ChatsMessages.scrollToBottom(chatModal.find('.chat-content'));
		module.bringModalToTop(chatModal);
		module.focusInput(chatModal);
		socket.emit('modules.chats.markRead', chatModal.attr('roomId'));
 
		var env = utils.findBootstrapEnvironment();
		if (env === 'xs' || env === 'sm') {
			module.enableMobileBehaviour(chatModal);
		}
	};
 
	module.enableMobileBehaviour = function (modalEl) {
		app.toggleNavbar(false);
		modalEl.attr('data-mobile', '1');
		var messagesEl = modalEl.find('.chat-content');
		messagesEl.css('height', module.calculateChatListHeight(modalEl));
 
		$(window).on('resize', function () {
			messagesEl.css('height', module.calculateChatListHeight(modalEl));
		});
	};
 
	module.disableMobileBehaviour = function () {
		app.toggleNavbar(true);
	};
 
	module.calculateChatListHeight = function (modalEl) {
		var totalHeight = modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight();
		var padding = parseInt(modalEl.find('.modal-body').css('padding-top'), 10) + parseInt(modalEl.find('.modal-body').css('padding-bottom'), 10);
		var contentMargin = parseInt(modalEl.find('.chat-content').css('margin-top'), 10) + parseInt(modalEl.find('.chat-content').css('margin-bottom'), 10);
		var inputGroupHeight = modalEl.find('.input-group').outerHeight();
 
		return totalHeight - padding - contentMargin - inputGroupHeight;
	};
 
	module.minimize = function (uuid) {
		var chatModal = $('div[UUID="' + uuid + '"]');
		chatModal.addClass('hide');
		taskbar.minimize('chat', uuid);
		clearInterval(chatModal.attr('intervalId'));
		chatModal.attr('intervalId', 0);
	};
 
	module.toggleNew = taskbar.toggleNew;
 
 
	return module;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/components.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/components.js

Statements: 4% (1 / 25)      Branches: 0% (0 / 6)      Functions: 0% (0 / 16)      Lines: 4% (1 / 25)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71  2                                                                                                                                          
"use strict";
define('components', function () {
	var components = {};
 
	components.core = {
		'topic/teaser': function (tid) {
			if (tid) {
				return $('[component="category/topic"][data-tid="' + tid + '"] [component="topic/teaser"]');
			} else {
				return $('[component="topic/teaser"]');
			}
		},
		'topic': function (name, value) {
			return $('[component="topic"][data-' + name + '="' + value + '"]');
		},
		'post': function (name, value) {
			return $('[component="post"][data-' + name + '="' + value + '"]');
		},
		'post/content': function (pid) {
			return components.core.post('pid', pid).find('[component="post/content"]');
		},
		'post/header': function (pid) {
			return components.core.post('pid', pid).find('[component="post/header"]');
		},
		'post/anchor': function (index) {
			return components.core.post('index', index).find('[component="post/anchor"]');
		},
		'post/vote-count': function (pid) {
			return components.core.post('pid', pid).find('[component="post/vote-count"]');
		},
		'post/bookmark-count': function (pid) {
			return components.core.post('pid', pid).find('[component="post/bookmark-count"]');
		},
 
		'user/postcount': function (uid) {
			return $('[component="user/postcount"][data-uid="' + uid + '"]');
		},
		'user/reputation': function (uid) {
			return $('[component="user/reputation"][data-uid="' + uid + '"]');
		},
 
		'category/topic': function (name, value) {
			return $('[component="category/topic"][data-' + name + '="' + value + '"]');
		},
 
		'categories/category': function (name, value) {
			return $('[component="categories/category"][data-' + name + '="' + value + '"]');
		},
 
		'chat/message': function (messageId) {
			return $('[component="chat/message"][data-mid="' + messageId + '"]');
		},
		'chat/message/body': function (messageId) {
			return $('[component="chat/message"][data-mid="' + messageId + '"] [component="chat/message/body"]');
		}
	};
 
	components.get = function () {
		var args = Array.prototype.slice.call(arguments, 1);
 
		if (components.core[arguments[0]] && args.length) {
			return components.core[arguments[0]].apply(this, args);
		} else {
			return $('[component="' + arguments[0] + '"]');
		}
	};
 
	return components;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/coverPhoto.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/coverPhoto.js

Statements: 4.88% (2 / 41)      Branches: 0% (0 / 8)      Functions: 0% (0 / 9)      Lines: 4.88% (2 / 41)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90      2                                                                                                     1                                                                      
"use strict";
/* globals define, app */
 
define('coverPhoto', [
	'vendor/jquery/draggable-background/backgroundDraggable'
], function () {
 
	var coverPhoto = {
		coverEl: null,
		saveFn: null
	};
 
	coverPhoto.init = function (coverEl, saveFn, uploadFn, removeFn) {
		coverPhoto.coverEl = coverEl;
		coverPhoto.saveFn = saveFn;
 
		coverEl.find('.upload').on('click', uploadFn);
		coverEl.find('.resize').on('click', function () {
			enableDragging(coverEl);
		});
		coverEl.find('.remove').on('click', removeFn);
 
		coverEl
			.on('dragover', coverPhoto.onDragOver)
			.on('drop', coverPhoto.onDrop);
 
		coverEl.find('.save').on('click', coverPhoto.save);
		coverEl.addClass('initialised');
	};
 
	coverPhoto.onDragOver = function (e) {
		e.stopPropagation();
		e.preventDefault();
		e.originalEvent.dataTransfer.dropEffect = 'copy';
	};
 
	coverPhoto.onDrop = function (e) {
		e.stopPropagation();
		e.preventDefault();
 
		var files = e.originalEvent.dataTransfer.files,
		reader = new FileReader();
 
		if (files.length && files[0].type.match('image.*')) {
			reader.onload = function (e) {
				coverPhoto.coverEl.css('background-image', 'url(' + e.target.result + ')');
				coverPhoto.newCover = e.target.result;
			};
 
			reader.readAsDataURL(files[0]);
			enableDragging(coverPhoto.coverEl);
		}
	};
 
	function enableDragging(coverEl) {
		coverEl.toggleClass('active', 1)
			.backgroundDraggable({
				axis: 'y',
				units: 'percent'
			});
 
		app.alert({
			alert_id: 'drag_start',
			title: '[[modules:cover.dragging_title]]',
			message: '[[modules:cover.dragging_message]]',
			timeout: 5000
		});
	}
 
	coverPhoto.save = function () {
		coverPhoto.coverEl.addClass('saving');
 
		coverPhoto.saveFn(coverPhoto.newCover || undefined, coverPhoto.coverEl.css('background-position'), function (err) {
			if (!err) {
				coverPhoto.coverEl.toggleClass('active', 0);
				coverPhoto.coverEl.backgroundDraggable('disable');
				coverPhoto.coverEl.off('dragover', coverPhoto.onDragOver);
				coverPhoto.coverEl.off('drop', coverPhoto.onDrop);
				app.alertSuccess('[[modules:cover.saved]]');
			} else {
				app.alertError(err.message);
			}
 
			coverPhoto.coverEl.removeClass('saving');
		});
	};
 
	return coverPhoto;
});
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/iconSelect.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/iconSelect.js

Statements: 3.92% (2 / 51)      Branches: 0% (0 / 22)      Functions: 0% (0 / 12)      Lines: 3.92% (2 / 51)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119        2                                                                                                                                             1                                                                                      
"use strict";
 
/* globals define, bootbox, templates */
 
define('iconSelect', function () {
	var iconSelect = {};
 
	iconSelect.init = function (el, onModified) {
		onModified = onModified || function () {};
		var doubleSize = el.hasClass('fa-2x'),
			selected = el.attr('class').replace('fa-2x', '').replace('fa', '').replace(/\s+/g, '');
 
		$('#icons .selected').removeClass('selected');
 
		if (selected) {
			$('#icons .fa-icons .fa.' + selected).addClass('selected');
		}
 
		templates.parse('partials/fontawesome', {}, function (html) {
			html = $(html);
			html.find('.fa-icons').prepend($('<i class="fa fa-nbb-none"></i>'));
 
			var picker = bootbox.dialog({
					onEscape: true,
					backdrop: true,
					show: false,
					message: html,
					title: 'Select an Icon',
					buttons: {
						noIcon: {
							label: 'No Icon',
							className: 'btn-default',
							callback: function () {
								el.attr('class', 'fa ' + (doubleSize ? 'fa-2x ' : ''));
								el.val('');
								el.attr('value', '');
 
								onModified(el);
							}
						},
						success: {
							label: 'Select',
							className: 'btn-primary',
							callback: function (confirm) {
								var iconClass = $('.bootbox .selected').attr('class');
								var categoryIconClass = $('<div/>').addClass(iconClass).removeClass('fa').removeClass('selected').attr('class');
 
								if (categoryIconClass) {
									el.attr('class', 'fa ' + (doubleSize ? 'fa-2x ' : '') + categoryIconClass);
									el.val(categoryIconClass);
									el.attr('value', categoryIconClass);
								}
 
								onModified(el);
							}
						}
					}
				});
 
			picker.on('show.bs.modal', function () {
				var modalEl = $(this),
					searchEl = modalEl.find('input');
 
				if (selected) {
					modalEl.find('.' + selected).addClass('selected');
					searchEl.val(selected.replace('fa-', ''));
				}
			}).modal('show');
 
			picker.on('shown.bs.modal', function () {
				var modalEl = $(this),
					searchEl = modalEl.find('input'),
					icons = modalEl.find('.fa-icons i'),
					submitEl = modalEl.find('button.btn-primary');
 
				function changeSelection(newSelection) {
					modalEl.find('i.selected').removeClass('selected');
					if (newSelection) {
						newSelection.addClass('selected');
					} else if (searchEl.val().length === 0) {
						if (selected) {
							modalEl.find('.' + selected).addClass('selected');
						}
					} else {
						modalEl.find('i:visible').first().addClass('selected');
					}
				}
 
				// Focus on the input box
				searchEl.selectRange(0, searchEl.val().length);
 
				modalEl.find('.icon-container').on('click', 'i', function () {
					searchEl.val($(this).attr('class').replace('fa fa-', '').replace('selected', ''));
					changeSelection($(this));
				});
 
				searchEl.on('keyup', function (e) {
					if (e.keyCode !== 13) {
						// Filter
						icons.show();
						icons.each(function (idx, el) {
							if (!el.className.match(new RegExp('^fa fa-.*' + searchEl.val() + '.*$'))) {
								$(el).hide();
							}
						});
						changeSelection();
					} else {
						submitEl.click();
					}
				});
			});
		});
	};
 
	return iconSelect;
});
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/navigator.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/navigator.js

Statements: 2.91% (5 / 172)      Branches: 0% (0 / 83)      Functions: 0% (0 / 28)      Lines: 2.91% (5 / 172)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304            2                                                                                                               1                                               1                                                                                                                                                                                                                                                                                                                                                   1                                                     1                                        
 
'use strict';
 
/* globals define, ajaxify, utils, config */
 
 
define('navigator', ['forum/pagination', 'components'], function (pagination, components) {
 
	var navigator = {};
	var index = 1;
	var count = 0;
	var navigatorUpdateTimeoutId = 0;
 
	navigator.scrollActive = false;
 
	navigator.init = function (selector, count, toTop, toBottom, callback, calculateIndex) {
		index = 1;
		navigator.selector = selector;
		navigator.callback = callback;
		toTop = toTop || function () {};
		toBottom = toBottom || function () {};
 
		$(window).off('scroll', navigator.delayedUpdate).on('scroll', navigator.delayedUpdate);
 
		$('.pagination-block .dropdown-menu').off('click').on('click', function (e) {
			e.stopPropagation();
		});
 
		$('.pagination-block').off('shown.bs.dropdown', '.dropdown').on('shown.bs.dropdown', '.dropdown', function () {
			setTimeout(function () {
				$('.pagination-block input').focus();
			}, 100);
		});
 
		$('.pagination-block .pageup').off('click').on('click', navigator.scrollUp);
		$('.pagination-block .pagedown').off('click').on('click', navigator.scrollDown);
		$('.pagination-block .pagetop').off('click').on('click', toTop);
		$('.pagination-block .pagebottom').off('click').on('click', toBottom);
 
		$('.pagination-block input').on('keydown', function (e) {
			if (e.which === 13) {
				var input = $(this);
				if (!utils.isNumber(input.val())) {
					input.val('');
					return;
				}
 
				var index = parseInt(input.val(), 10);
				if (typeof calculateIndex === 'function') {
					index = calculateIndex(index, count);
				}
 
				var url = generateUrl(index);
				input.val('');
				$('.pagination-block .dropdown-toggle').trigger('click');
				ajaxify.go(url);
			}
		});
 
		navigator.setCount(count);
	};
 
	function generateUrl(index) {
		var pathname = window.location.pathname.replace(config.relative_path, '');
		var parts = pathname.split('/');
		return parts[1] + '/' + parts[2] + '/' + parts[3] + (index ? '/' + index : '');
	}
 
	navigator.setCount = function (value) {
		count = parseInt(value, 10);
		navigator.updateTextAndProgressBar();
	};
 
	navigator.show = function () {
		toggle(true);
	};
 
	navigator.disable = function () {
		count = 0;
		index = 1;
		navigator.selector = navigator.callback = null;
		$(window).off('scroll', navigator.update);
 
		toggle(false);
	};
 
	function toggle(flag) {
		var path = ajaxify.removeRelativePath(window.location.pathname.slice(1));
		if (flag && (!path.startsWith('topic') && !path.startsWith('category'))) {
			return;
		}
 
		$('.pagination-block').toggleClass('ready', flag);
	}
 
	navigator.delayedUpdate = function () {
		if (navigatorUpdateTimeoutId) {
			clearTimeout(navigatorUpdateTimeoutId);
			navigatorUpdateTimeoutId = 0;
		}
		navigatorUpdateTimeoutId = setTimeout(navigator.update, 100);
	};
 
	navigator.update = function (threshold) {
		/*
			The "threshold" is defined as the distance from the top of the page to
			a spot where a user is expecting to begin reading.
		*/
		threshold = typeof threshold === 'number' ? threshold : undefined;
 
		var els = $(navigator.selector);
		if (els.length) {
			index = parseInt(els.first().attr('data-index'), 10) + 1;
		}
 
		var scrollTop = $(window).scrollTop();
		var windowHeight = $(window).height();
		var documentHeight = $(document).height();
		var middleOfViewport = scrollTop + windowHeight / 2;
		var previousDistance = Number.MAX_VALUE;
		els.each(function () {
			var distanceToMiddle = Math.abs(middleOfViewport - $(this).offset().top);
 
			if (distanceToMiddle > previousDistance) {
				return false;
			}
 
			if (distanceToMiddle < previousDistance) {
				index = parseInt($(this).attr('data-index'), 10) + 1;
				previousDistance = distanceToMiddle;
			}
		});
 
		var atTop = scrollTop === 0 && parseInt(els.first().attr('data-index'), 10) === 0;
		var nearBottom = scrollTop + windowHeight > documentHeight - 100 && parseInt(els.last().attr('data-index'), 10) === count - 1;
 
		if (atTop) {
			index = 1;
		} else if (nearBottom) {
			index = count;
		}
 
		// If a threshold is undefined, try to determine one based on new index
		if (threshold === undefined && ajaxify.data.template.topic === true) {
			if (atTop) {
				threshold = 0;
			} else {
				var anchorEl = components.get('post/anchor', index - 1);
				if (anchorEl.length) {
					var anchorRect = anchorEl.get(0).getBoundingClientRect();
					threshold = anchorRect.top;
				}
			}
		}
 
		if (typeof navigator.callback === 'function') {
			navigator.callback(index, count, threshold);
		}
 
		navigator.updateTextAndProgressBar();
		toggle(!!count);
	};
 
	navigator.updateTextAndProgressBar = function () {
		if (!utils.isNumber(index)) {
			return;
		}
		index = index > count ? count : index;
 
		$('.pagination-block .pagination-text').translateHtml('[[global:pagination.out_of, ' + index + ', ' + count + ']]');
		$('.pagination-block .progress-bar').width((index / count * 100) + '%');
	};
 
	navigator.scrollUp = function () {
		$('body,html').animate({
			scrollTop: $(window).scrollTop() - $(window).height()
		});
	};
 
	navigator.scrollDown = function () {
		$('body,html').animate({
			scrollTop: $(window).scrollTop() + $(window).height()
		});
	};
 
	navigator.scrollTop = function (index) {
		if ($(navigator.selector + '[data-index="' + index + '"]').length) {
			navigator.scrollToPost(index, true);
		} else {
			ajaxify.go(generateUrl());
		}
	};
 
	navigator.scrollBottom = function (index) {
		if (parseInt(index, 10) < 0) {
			return;
		}
		if ($(navigator.selector + '[data-index="' + index + '"]').length) {
			navigator.scrollToPost(index, true);
		} else {
			index = parseInt(index, 10) + 1;
			ajaxify.go(generateUrl(index));
		}
	};
 
	navigator.scrollToPost = function (postIndex, highlight, duration) {
		if (!utils.isNumber(postIndex) || !components.get('topic').length) {
			return;
		}
 
		duration = duration !== undefined ? duration : 400;
		navigator.scrollActive = true;
 
		if (components.get('post/anchor', postIndex).length) {
			return navigator.scrollToPostIndex(postIndex, highlight, duration);
		}
 
		if (config.usePagination) {
			var index = postIndex;
			if (config.topicPostSort === 'most_votes' || config.topicPostSort === 'newest_to_oldest') {
				index = ajaxify.data.postcount - index;
			}
			var page = Math.max(1, Math.ceil(index / config.postsPerPage));
 
			if (parseInt(page, 10) !== ajaxify.data.pagination.currentPage) {
				pagination.loadPage(page, function () {
					navigator.scrollToPostIndex(postIndex, highlight, duration);
				});
			} else {
				navigator.scrollToPostIndex(postIndex, highlight, duration);
			}
		} else {
			navigator.scrollActive = false;
			postIndex = parseInt(postIndex, 10) + 1;
			ajaxify.go(generateUrl(postIndex));
		}
	};
 
	navigator.scrollToPostIndex = function (postIndex, highlight, duration) {
		var scrollTo = components.get('post', 'index', postIndex);
		var postHeight = scrollTo.height();
		var viewportHeight = $(window).height();
		var navbarHeight = components.get('navbar').height();
 
		if (!scrollTo.length) {
			navigator.scrollActive = false;
			return;
		}
 
		// Temporarily disable navigator update on scroll
		$(window).off('scroll', navigator.update);
 
		duration = duration !== undefined ? duration : 400;
		navigator.scrollActive = true;
		var done = false;
 
		function animateScroll() {
			var scrollTop = 0;
			if (postHeight < viewportHeight) {
				scrollTop = (scrollTo.offset().top - (viewportHeight / 2) + (postHeight / 2));
			} else {
				scrollTop = scrollTo.offset().top - navbarHeight;
			}
 
			$('html, body').animate({
				scrollTop: scrollTop + 'px'
			}, duration, function () {
				if (done) {
					// Re-enable onScroll behaviour
					$(window).on('scroll', navigator.update);
					var scrollToRect = scrollTo.get(0).getBoundingClientRect();
					navigator.update(scrollToRect.top);
					return;
				}
				done = true;
 
				navigator.scrollActive = false;
				highlightPost();
				$('body').scrollTop($('body').scrollTop() - 1);
				$('html').scrollTop($('html').scrollTop() - 1);
			});
		}
 
		function highlightPost() {
			if (highlight) {
				scrollTo.addClass('highlight');
				setTimeout(function () {
					scrollTo.removeClass('highlight');
				}, 10000);
			}
		}
 
		if (components.get('topic').length) {
			animateScroll();
		} else {
			navigator.scrollActive = false;
		}
	};
 
 
	return navigator;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/notifications.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/notifications.js

Statements: 2.11% (2 / 95)      Branches: 0% (0 / 46)      Functions: 0% (0 / 20)      Lines: 2.11% (2 / 95)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182        2                                                                                                                                 1                                                                                                                                                                                                                                
'use strict';
 
/* globals define, socket, app, ajaxify, templates, Tinycon*/
 
define('notifications', ['sounds', 'translator', 'components'], function (sound, translator, components) {
	var Notifications = {};
 
	var unreadNotifs = {};
 
	Notifications.prepareDOM = function () {
		var notifContainer = components.get('notifications'),
			notifTrigger = notifContainer.children('a'),
			notifList = components.get('notifications/list'),
			notifIcon = components.get('notifications/icon');
 
		notifTrigger
			.on('click', function (e) {
				e.preventDefault();
				if (notifContainer.hasClass('open')) {
					return;
				}
 
				Notifications.loadNotifications(notifList);
			})
			.on('dblclick', function (e) {
				e.preventDefault();
				if (parseInt(notifIcon.attr('data-content'), 10) > 0) {
					Notifications.markAllRead();
				}
			});
 
		notifList.on('click', '[data-nid]', function () {
			var unread = $(this).hasClass('unread');
			var nid = $(this).attr('data-nid');
			if (!unread) {
				return;
			}
			socket.emit('notifications.markRead', nid, function (err) {
				if (err) {
					return app.alertError(err.message);
				}
				incrementNotifCount(-1);
				if (unreadNotifs[nid]) {
					delete unreadNotifs[nid];
				}
			});
		});
 
		notifContainer.on('click', '.mark-all-read', Notifications.markAllRead);
 
		notifList.on('click', '.mark-read', function () {
			var liEl = $(this).parent();
			var unread = liEl.hasClass('unread');
			var nid = liEl.attr('data-nid');
 
			socket.emit('notifications.mark' + (unread ? 'Read' : 'Unread'), nid, function (err) {
				if (err) {
					return app.alertError(err.message);
				}
 
				liEl.toggleClass('unread');
				incrementNotifCount(unread ? -1 : 1);
				if (unread && unreadNotifs[nid]) {
					delete unreadNotifs[nid];
				}
			});
			return false;
		});
 
		function incrementNotifCount(delta) {
			var count = parseInt(notifIcon.attr('data-content'), 10) + delta;
			Notifications.updateNotifCount(count);
		}
 
		socket.on('event:new_notification', function (notifData) {
			// If a path is defined, show notif data, otherwise show generic data
			var payload = {
				alert_id: 'new_notif',
				title: '[[notifications:new_notification]]',
				timeout: 2000
			};
 
			if (notifData.path) {
				payload.message = notifData.bodyShort;
				payload.type = 'info';
				payload.clickfn = function () {
					if (notifData.path.startsWith('http') && notifData.path.startsWith('https')) {
						window.location.href = notifData.path;
					} else {
						window.location.href = window.location.protocol + '//' + window.location.host + config.relative_path + notifData.path;
					}
				};
			} else {
				payload.message = '[[notifications:you_have_unread_notifications]]';
				payload.type = 'warning';
			}
 
			app.alert(payload);
			app.refreshTitle();
 
			if (ajaxify.currentPage === 'notifications') {
				ajaxify.refresh();
			}
 
			socket.emit('notifications.getCount', function (err, count) {
				if (err) {
					return app.alertError(err.message);
				}
 
				Notifications.updateNotifCount(count);
			});
 
			if (!unreadNotifs[notifData.nid]) {
				sound.play('notification');
				unreadNotifs[notifData.nid] = true;
			}
		});
 
		socket.on('event:notifications.updateCount', function (count) {
			Notifications.updateNotifCount(count);
		});
	};
 
	Notifications.loadNotifications = function (notifList) {
		socket.emit('notifications.get', null, function (err, data) {
			if (err) {
				return app.alertError(err.message);
			}
 
			var notifs = data.unread.concat(data.read).sort(function (a, b) {
				return parseInt(a.datetime, 10) > parseInt(b.datetime, 10) ? -1 : 1;
			});
 
			translator.toggleTimeagoShorthand();
			for(var i = 0; i < notifs.length; ++i) {
				notifs[i].timeago = $.timeago(new Date(parseInt(notifs[i].datetime, 10)));
			}
			translator.toggleTimeagoShorthand();
 
			templates.parse('partials/notifications_list', {notifications: notifs}, function (html) {
				notifList.translateHtml(html);
			});
		});
	};
 
	Notifications.updateNotifCount = function (count) {
		var notifIcon = components.get('notifications/icon');
		count = Math.max(0, count);
		if (count > 0) {
			notifIcon.removeClass('fa-bell-o').addClass('fa-bell');
		} else {
			notifIcon.removeClass('fa-bell').addClass('fa-bell-o');
		}
 
		notifIcon.toggleClass('unread-count', count > 0);
		notifIcon.attr('data-content', count > 99 ? '99+' : count);
 
		var payload = {
			count: count,
			updateFavicon: true
		};
		$(window).trigger('action:notification.updateCount', payload);
 
		if (payload.updateFavicon) {
			Tinycon.setBubble(count > 99 ? '99+' : count);
		}
	};
 
	Notifications.markAllRead = function () {
		socket.emit('notifications.markAllRead', function (err) {
			if (err) {
				app.alertError(err.message);
			}
			Notifications.updateNotifCount(0);
			unreadNotifs = {};
		});
	};
 
	return Notifications;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/postSelect.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/postSelect.js

Statements: 13.33% (4 / 30)      Branches: 0% (0 / 8)      Functions: 0% (0 / 8)      Lines: 13.79% (4 / 29)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60        2                           1                                                 1       1                        
'use strict';
 
/* globals define*/
 
define('postSelect', ['components'], function (components) {
	var PostSelect = {};
 
	PostSelect.pids = [];
 
	PostSelect.init = function (onSelect) {
		PostSelect.pids.length = 0;
		components.get('topic').on('click', '[data-pid]', function () {
			togglePostSelection($(this), onSelect);
		});
		disableClicksOnPosts();
	};
 
 
	function togglePostSelection(post, callback) {
		var newPid = post.attr('data-pid');
 
		if (parseInt(post.attr('data-index'), 10) === 0) {
			return;
		}
 
		if (newPid) {
			var index = PostSelect.pids.indexOf(newPid);
			if(index === -1) {
				PostSelect.pids.push(newPid);
				post.toggleClass('bg-success', true);
			} else {
				PostSelect.pids.splice(index, 1);
				post.toggleClass('bg-success', false);
			}
 
			if (PostSelect.pids.length) {
				PostSelect.pids.sort(function (a,b) { return a - b; });
			}
			callback();
		}
	}
 
 
	function disableClicks() {
		return false;
	}
 
	function disableClicksOnPosts() {
		components.get('post').on('click', 'button,a', disableClicks);
	}
 
	PostSelect.enableClicksOnPosts = function () {
		components.get('post').off('click', 'button,a', disableClicks);
	};
 
 
 
	return PostSelect;
});
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/scrollStop.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/scrollStop.js

Statements: 10% (1 / 10)      Branches: 0% (0 / 6)      Functions: 0% (0 / 3)      Lines: 10% (1 / 10)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33                        2                                        
'use strict';
 
/* globals console, define */
 
/*
	The point of this library is to enhance(tm) a textarea so that if scrolled,
	you can only scroll to the top of it and the event doesn't bubble up to
	the document... because it does... and it's annoying at times.
 
	While I'm here, might I say this is a solved issue on Linux?
*/
 
define('scrollStop', function () {
	var Module = {};
 
	Module.apply = function (element) {
		$(element).on('mousewheel', function (e) {
			var scrollTop = this.scrollTop;
			var scrollHeight = this.scrollHeight;
			var elementHeight = this.getBoundingClientRect().height;
			
			if (
				(e.originalEvent.deltaY < 0 && scrollTop === 0) ||							// scroll up
				(e.originalEvent.deltaY > 0 && (elementHeight + scrollTop) > scrollHeight)	// scroll down
			) {
				return false;
			}
		});
	};
 
	return Module;
});
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/search.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/search.js

Statements: 2.3% (2 / 87)      Branches: 0% (0 / 50)      Functions: 0% (0 / 18)      Lines: 2.3% (2 / 87)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185      2                                                                 1                                                                                                                                                                                                                                                                                                        
"use strict";
/* globals socket, ajaxify, app, define, config */
 
define('search', ['navigator', 'translator'], function (nav, translator) {
 
	var Search = {
		current: {}
	};
 
	Search.query = function (data, callback) {
		var term = data.term;
 
		// Detect if a tid was specified
		var topicSearch = term.match(/^in:topic-([\d]+) /);
 
		if (!topicSearch) {
			term = term.replace(/^[ ?#]*/, '');
 
			try {
				term = encodeURIComponent(term);
			} catch(e) {
				return app.alertError('[[error:invalid-search-term]]');
			}
 
			ajaxify.go('search?' + createQueryString(data));
			callback();
		} else {
			var cleanedTerm = term.replace(topicSearch[0], '');
			var tid = topicSearch[1];
 
			if (cleanedTerm.length > 0) {
				Search.queryTopic(tid, cleanedTerm, callback);
			}
		}
	};
 
	function createQueryString(data) {
		var searchIn = data['in'] || 'titlesposts';
		var postedBy = data.by || '';
		var query = {
			term: data.term,
			'in': searchIn
		};
 
		if (postedBy && (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts')) {
			query.by = postedBy;
		}
 
		if (data.categories && data.categories.length) {
			query.categories = data.categories;
			if (data.searchChildren) {
				query.searchChildren = data.searchChildren;
			}
		}
 
		if (parseInt(data.replies, 10) > 0) {
			query.replies = data.replies;
			query.repliesFilter = data.repliesFilter || 'atleast';
		}
 
		if (data.timeRange) {
			query.timeRange = data.timeRange;
			query.timeFilter = data.timeFilter || 'newer';
		}
 
		if (data.sortBy) {
			query.sortBy = data.sortBy;
			query.sortDirection = data.sortDirection;
		}
 
		if (data.showAs) {
			query.showAs = data.showAs;
		}
		return decodeURIComponent($.param(query));
	}
 
	Search.getSearchPreferences = function () {
		try {
			return JSON.parse(localStorage.getItem('search-preferences') || '{}');
		} catch(e) {
			return {};
		}
	};
 
	Search.queryTopic = function (tid, term, callback) {
		socket.emit('topics.search', {
			tid: tid,
			term: term
		}, function (err, pids) {
			if (err) {
				return app.alertError(err.message);
			}
 
			if (Array.isArray(pids)) {
				// Sort pids numerically & store
				Search.current = {
					results: pids.sort(function (a, b) {
						return a - b;
					}),
					tid: tid,
					term: term
				};
 
				Search.checkPagePresence(tid, function () {
					Search.topicDOM.update(0);
				});
			}
		});
	};
 
	Search.checkPagePresence = function (tid, callback) {
		if (parseInt(ajaxify.data.tid, 10) !== parseInt(tid, 10)) {
			ajaxify.go('topic/' + tid, callback);
		} else {
			callback();
		}
	};
 
	Search.topicDOM = {
		active: false
	};
 
	Search.topicDOM.prev = function () {
		Search.topicDOM.update((Search.current.index === 0) ? Search.current.results.length - 1 : Search.current.index - 1);
	};
 
	Search.topicDOM.next = function () {
		Search.topicDOM.update((Search.current.index === Search.current.results.length - 1) ? 0 : Search.current.index + 1);
	};
 
	Search.topicDOM.update = function (index) {
		var topicSearchEl = $('.topic-search');
		Search.current.index = index;
 
		Search.topicDOM.start();
 
		if (Search.current.results.length > 0) {
			topicSearchEl.find('.count').html((index + 1) + ' / ' + Search.current.results.length);
			topicSearchEl.find('.prev, .next').removeAttr('disabled');
			var data = {
				pid: Search.current.results[index],
				tid: Search.current.tid,
				topicPostSort: config.topicPostSort
			};
			socket.emit('posts.getPidIndex', data, function (err, postIndex) {
				if (err) {
					return app.alertError(err.message);
				}
 
				nav.scrollToPost(postIndex, true);
			});
		} else {
			translator.translate('[[search:no-matches]]', function (text) {
				topicSearchEl.find('.count').html(text);
			});
			topicSearchEl.removeClass('hidden').find('.prev, .next').attr('disabled', 'disabled');
		}
	};
 
	Search.topicDOM.start = function () {
		$('.topic-search').removeClass('hidden');
		if (!Search.topicDOM.active) {
			Search.topicDOM.active = true;
 
			// Bind to esc
			require(['mousetrap'], function (mousetrap) {
				mousetrap.bind('esc', Search.topicDOM.end);
			});
		}
	};
 
	Search.topicDOM.end = function () {
		$('.topic-search').addClass('hidden').find('.prev, .next').attr('disabled', 'disabled');
		Search.topicDOM.active = false;
 
		// Unbind esc
		require(['mousetrap'], function (mousetrap) {
			mousetrap.unbind('esc', Search.topicDOM.end);
		});
	};
 
	return Search;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/settings.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/settings.js

Statements: 0.97% (2 / 207)      Branches: 0% (0 / 174)      Functions: 0% (0 / 38)      Lines: 0.97% (2 / 207)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545      2                                           1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
"use strict";
/*global define, app, socket*/
 
define('settings', function () {
 
	var DEFAULT_PLUGINS = [
		'settings/checkbox',
		'settings/number',
		'settings/textarea',
		'settings/select',
		'settings/array',
		'settings/key',
		'settings/object'
	];
 
	var Settings,
		onReady = [],
		waitingJobs = 0,
		helper;
 
	/**
	 Returns the hook of given name that matches the given type or element.
	 @param type The type of the element to get the matching hook for, or the element itself.
	 @param name The name of the hook.
	 */
	function getHook(type, name) {
		var hook, plugin;
		if (typeof type !== 'string') {
			type = $(type);
			type = type.data('type') || type.attr('type') || type.prop('tagName');
		}
		plugin = Settings.plugins[type.toLowerCase()];
		if (plugin == null) {
			return void 0;
		}
		hook = plugin[name];
		if (typeof hook === 'function') {
			return hook;
		} else {
			return null;
		}
	}
 
	helper = {
		/**
		 @returns Object A deep clone of the given object.
		 */
		deepClone: function (obj) {
			if (typeof obj === 'object') {
				return JSON.parse(JSON.stringify(obj));
			} else {
				return obj;
			}
		},
		/**
		 Creates a new Element with given data.
		 @param tagName The tag-name of the element to create.
		 @param data The attributes to set.
		 @param text The text to add into the element.
		 @returns HTMLElement The created element.
		 */
		createElement: function (tagName, data, text) {
			var element = document.createElement(tagName);
			for (var k in data) {
				if (data.hasOwnProperty(k)) {
					element.setAttribute(k, data[k]);
				}
			}
			if (text) {
				element.appendChild(document.createTextNode(text));
			}
			return element;
		},
		/**
		 Calls the init-hook of the given element.
		 @param element The element to initialize.
		 */
		initElement: function (element) {
			var hook = getHook(element, 'init');
			if (hook != null) {
				hook.call(Settings, $(element));
			}
		},
		/**
		 Calls the destruct-hook of the given element.
		 @param element The element to destruct.
		 */
		destructElement: function (element) {
			var hook = getHook(element, 'destruct');
			if (hook != null) {
				hook.call(Settings, $(element));
			}
		},
		/**
		 Creates and initializes a new element.
		 @param type The type of the new element.
		 @param tagName The tag-name of the new element.
		 @param data The data to forward to create-hook or use as attributes.
		 @returns JQuery The created element.
		 */
		createElementOfType: function (type, tagName, data) {
			var element, hook = getHook(type, 'create');
			if (hook != null) {
				element = $(hook.call(Settings, type, tagName, data));
			} else {
				if (data == null) {
					data = {};
				}
				if (type != null) {
					data.type = type;
				}
				element = $(helper.createElement(tagName || 'input', data));
			}
			element.data('type', type);
			helper.initElement(element);
			return element;
		},
		/**
		 Creates a new Array that contains values of given Array depending on trim and empty.
		 @param array The array to clean.
		 @param trim Whether to trim each value if it has a trim-function.
		 @param empty Whether empty values should get added.
		 @returns Array The filtered and/or modified Array.
		 */
		cleanArray: function (array, trim, empty) {
			var cleaned = [];
			if (!trim && empty) {
				return array;
			}
			for (var i = 0; i < array.length; i++) {
				var value = array[i];
				if (trim) {
					value = value === true ? 1 : value === false ? 0 : typeof value.trim === 'function' ? value.trim() : value;
				}
				if (empty || (value != null ? value.length : void 0)) {
					cleaned.push(value);
				}
			}
			return cleaned;
		},
		isTrue: function (value) {
			return value === 'true' || +value === 1;
		},
		isFalse: function (value) {
			return value === 'false' || +value === 0;
		},
		/**
		 Calls the get-hook of the given element and returns its result.
		 If no hook is specified it gets treated as input-field.
		 @param element The element of that the value should get read.
		 @returns Object The value of the element.
		 */
		readValue: function (element) {
			var empty = !helper.isFalse(element.data('empty')),
				trim = !helper.isFalse(element.data('trim')),
				split = element.data('split'),
				hook = getHook(element, 'get'),
				value;
			if (hook != null) {
				return hook.call(Settings, element, trim, empty);
			}
			if (split != null) {
				empty = helper.isTrue(element.data('empty')); // default empty-value is false for arrays
				value = element.val();
				var array = (value != null ? value.split(split || ',') : void 0) || [];
				return helper.cleanArray(array, trim, empty);
			} else {
				value = element.val();
				if (trim && value != null && typeof value.trim === 'function') {
					value = value.trim();
				}
				if (empty || value !== void 0 && (value == null || value.length !== 0)) {
					return value;
				} else {
					return void 0;
				}
			}
		},
		/**
		 Calls the set-hook of the given element.
		 If no hook is specified it gets treated as input-field.
		 @param element The JQuery-Object of the element to fill.
		 @param value The value to set.
		 */
		fillField: function (element, value) {
			var hook = getHook(element, 'set'),
				trim = element.data('trim');
			trim = trim !== 'false' && +trim !== 0;
			if (hook != null) {
				return hook.call(Settings, element, value, trim);
			}
			if (value instanceof Array) {
				value = value.join(element.data('split') || (trim ? ', ' : ','));
			}
			if (trim && value && typeof value.trim === 'function') {
				value = value.trim();
				if (typeof value.toString === 'function') {
					value = value.toString();
				}
			} else if (value != null) {
				if (typeof value.toString === 'function') {
					value = value.toString();
				}
				if (trim) {
					value = value.trim();
				}
			} else {
				value = '';
			}
			if (value !== void 0) {
				element.val(value);
			}
		},
		/**
		 Calls the init-hook and {@link helper.fillField} on each field within wrapper-object.
		 @param wrapper The wrapper-element to set settings within.
		 */
		initFields: function (wrapper) {
			$('[data-key]', wrapper).each(function (ignored, field) {
				field = $(field);
				var hook = getHook(field, 'init'),
					keyParts = field.data('key').split('.'),
					value = Settings.get();
				if (hook != null) {
					hook.call(Settings, field);
				}
				for (var i = 0; i < keyParts.length; i++) {
					var part = keyParts[i];
					if (part && value != null) {
						value = value[part];
					}
				}
				helper.fillField(field, value);
			});
		},
		/**
		 Increases the amount of jobs before settings are ready by given amount.
		 @param amount The amount of jobs to register.
		 */
		registerReadyJobs: function (amount) {
			return waitingJobs += amount;
		},
		/**
		 Decreases the amount of jobs before settings are ready by given amount or 1.
		 If the amount is less or equal 0 all callbacks registered by {@link helper.whenReady} get called.
		 @param amount The amount of jobs that finished.
		 */
		beforeReadyJobsDecreased: function (amount) {
			if (amount == null) {
				amount = 1;
			}
			if (waitingJobs > 0) {
				waitingJobs -= amount;
				if (waitingJobs <= 0) {
					for (var i = 0; i < onReady.length; i++) {
						onReady[i]();
					}
					onReady = [];
				}
			}
		},
		/**
		 Calls the given callback when the settings are ready.
		 @param callback The callback.
		 */
		whenReady: function (callback) {
			if (waitingJobs <= 0) {
				callback();
			} else {
				onReady.push(callback);
			}
		},
		/**
		 Persists the given settings with given hash.
		 @param hash The hash to use as settings-id.
		 @param settings The settings-object to persist.
		 @param notify Whether to send notification when settings got saved.
		 @param callback The callback to call when done.
		 */
		persistSettings: function (hash, settings, notify, callback) {
			if (settings != null && settings._ != null && typeof settings._ !== 'string') {
				settings = helper.deepClone(settings);
				settings._ = JSON.stringify(settings._);
			}
			socket.emit('admin.settings.set', {
				hash: hash,
				values: settings
			}, function (err) {
				if (notify) {
					if (err) {
						app.alert({
							title: 'Settings Not Saved',
							type: 'danger',
							message: "NodeBB failed to save the settings.",
							timeout: 5000
						});
					} else {
						app.alert({
							title: 'Settings Saved',
							type: 'success',
							message: "Settings have been successfully saved",
							timeout: 2500
						});
					}
				}
				if (typeof callback === 'function') {
					callback(err);
				}
			});
		},
		/**
		 Sets the settings to use to given settings.
		 @param settings The settings to use.
		 */
		use: function (settings) {
			try {
				settings._ = JSON.parse(settings._);
			} catch (_error) {}
			Settings.cfg = settings;
		}
	};
 
 
	Settings = {
		helper: helper,
		plugins: {},
		cfg: {},
 
		/**
		 Returns the saved settings.
		 @returns Object The settings.
		 */
		get: function () {
			if (Settings.cfg != null && Settings.cfg._ !== void 0) {
				return Settings.cfg._;
			}
			return Settings.cfg;
		},
		/**
		 Registers a new plugin and calls its use-hook.
		 @param service The plugin to register.
		 @param types The types to bind the plugin to.
		 */
		registerPlugin: function (service, types) {
			if (types == null) {
				types = service.types;
			} else {
				service.types = types;
			}
			if (typeof service.use === 'function') {
				service.use.call(Settings);
			}
			for (var i = 0; i < types.length; i++) {
				var type = types[i].toLowerCase();
				if (Settings.plugins[type] == null) {
					Settings.plugins[type] = service;
				}
			}
		},
		/**
		 Sets the settings to given ones, resets the fields within given wrapper and saves the settings server-side.
		 @param hash The hash to use as settings-id.
		 @param settings The settings to set.
		 @param wrapper The wrapper-element to find settings within.
		 @param callback The callback to call when done.
		 @param notify Whether to send notification when settings got saved.
		 */
		set: function (hash, settings, wrapper, callback, notify) {
			if (notify == null) {
				notify = true;
			}
			helper.whenReady(function () {
				helper.use(settings);
				helper.initFields(wrapper || 'form');
				helper.persistSettings(hash, settings, notify, callback);
			});
		},
		/**
		 Fetches the settings from server and calls {@link Settings.helper.initFields} once the settings are ready.
		 @param hash The hash to use as settings-id.
		 @param wrapper The wrapper-element to set settings within.
		 @param callback The callback to call when done.
		 */
		sync: function (hash, wrapper, callback) {
			socket.emit('admin.settings.get', {
				hash: hash
			}, function (err, values) {
				if (err) {
					if (typeof callback === 'function') {
						callback(err);
					}
				} else {
					helper.whenReady(function () {
						helper.use(values);
						helper.initFields(wrapper || 'form');
						if (typeof callback === 'function') {
							callback();
						}
					});
				}
			});
		},
		/**
		 Reads the settings from fields and saves them server-side.
		 @param hash The hash to use as settings-id.
		 @param wrapper The wrapper-element to find settings within.
		 @param callback The callback to call when done.
		 @param notify Whether to send notification when settings got saved.
		 */
		persist: function (hash, wrapper, callback, notify) {
			var notSaved = [],
				fields = $('[data-key]', wrapper || 'form').toArray();
			if (notify == null) {
				notify = true;
			}
			for (var i = 0; i < fields.length; i++) {
				var field = $(fields[i]),
					value = helper.readValue(field),
					parentCfg = Settings.get(),
					keyParts = field.data('key').split('.'),
					lastKey = keyParts[keyParts.length - 1];
				if (keyParts.length > 1) {
					for (var j = 0; j < keyParts.length - 1; j++) {
						var part = keyParts[j];
						if (part && parentCfg != null) {
							parentCfg = parentCfg[part];
						}
					}
				}
				if (parentCfg != null) {
					if (value != null) {
						parentCfg[lastKey] = value;
					} else {
						delete parentCfg[lastKey];
					}
				} else {
					notSaved.push(field.data('key'));
				}
			}
			if (notSaved.length) {
				app.alert({
					title: 'Attributes Not Saved',
					message: "'" + (notSaved.join(', ')) + "' could not be saved. Please contact the plugin-author!",
					type: 'danger',
					timeout: 5000
				});
			}
			helper.persistSettings(hash, Settings.cfg, notify, callback);
		},
		load: function (hash, formEl, callback) {
			callback = callback || function () {};
			socket.emit('admin.settings.get', {
				hash: hash
			}, function (err, values) {
				if (err) {
					return callback(err);
				}
 
				// Parse all values. If they are json, return json
				for(var key in values) {
					if (values.hasOwnProperty(key)) {
						try {
							values[key] = JSON.parse(values[key]);
						} catch (e) {
							// Leave the value as is
						}
					}
				}
 
				$(formEl).deserialize(values);
				$(formEl).find('input[type="checkbox"]').each(function () {
					$(this).parents('.mdl-switch').toggleClass('is-checked', $(this).is(':checked'));
				});
				$(window).trigger('action:admin.settingsLoaded');
 
				// Handle unsaved changes
				$(formEl).on('change', 'input, select, textarea', function () {
					app.flags = app.flags || {};
					app.flags._unsaved = true;
				});
 
				callback(null, values);
			});
		},
		save: function (hash, formEl, callback) {
			formEl = $(formEl);
			if (formEl.length) {
				var values = formEl.serializeObject();
				// "Fix" checkbox values, so that unchecked options are not omitted
				formEl.find('input[type="checkbox"]').each(function (idx, inputEl) {
					inputEl = $(inputEl);
					if (!inputEl.is(':checked')) {
						values[inputEl.attr('name')] = 'off';
					}
				});
 
				// Normalizing value of multiple selects
				formEl.find('select[multiple]').each(function (idx, selectEl) {
					selectEl = $(selectEl);
					values[selectEl.attr('name')] = JSON.stringify(selectEl.val());
				});
 
				socket.emit('admin.settings.set', {
					hash: hash,
					values: values
				}, function (err) {
					// Remove unsaved flag to re-enable ajaxify
					app.flags._unsaved = false;
 
					if (typeof callback === 'function') {
						callback(err);
					} else {
						if (err) {
							app.alert({
								title: 'Error while saving settings',
								type: 'error',
								timeout: 2500
							});
						} else {
							app.alert({
								title: 'Settings Saved',
								type: 'success',
								timeout: 2500
							});
						}
					}
				});
			}
		}
	};
 
 
	helper.registerReadyJobs(1);
	require(DEFAULT_PLUGINS, function () {
		for (var i = 0; i < arguments.length; i++) {
			Settings.registerPlugin(arguments[i]);
		}
		helper.beforeReadyJobsDecreased();
	});
 
	return Settings;
 
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/share.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/share.js

Statements: 14.29% (4 / 28)      Branches: 0% (0 / 2)      Functions: 0% (0 / 11)      Lines: 14.29% (4 / 28)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61        2               1                                                                       1       1                
'use strict';
 
/* globals define */
 
define('share', function () {
 
	var module = {};
 
	module.addShareHandlers = function (name) {
 
		var baseUrl = window.location.protocol + '//' + window.location.host;
 
		function openShare(url, urlToPost, width, height) {
			window.open(url + encodeURIComponent(baseUrl + urlToPost), '_blank', 'width=' + width + ',height=' + height + ',scrollbars=no,status=no');
			return false;
		}
 
		$('#content').off('shown.bs.dropdown', '.share-dropdown').on('shown.bs.dropdown', '.share-dropdown', function () {
 
			var postLink = $(this).find('.post-link');
			postLink.val(baseUrl + getPostUrl($(this)));
 
			// without the setTimeout can't select the text in the input
			setTimeout(function () {
				postLink.putCursorAtEnd().select();
			}, 50);
		});
 
		addHandler('.post-link', function (e) {
			e.preventDefault();
			return false;
		});
 
		addHandler('[component="share/twitter"]', function () {
			return openShare('https://twitter.com/intent/tweet?text=' + encodeURIComponent(name) + '&url=', getPostUrl($(this)), 550, 420);
		});
 
		addHandler('[component="share/facebook"]', function () {
			return openShare('https://www.facebook.com/sharer/sharer.php?u=', getPostUrl($(this)), 626, 436);
		});
 
		addHandler('[component="share/google"]', function () {
			return openShare('https://plus.google.com/share?url=', getPostUrl($(this)), 500, 570);
		});
 
		$(window).trigger('action:share.addHandlers', {openShare: openShare});
	};
 
	function addHandler(selector, callback) {
		$('#content').off('click', selector).on('click', selector, callback);
	}
 
	function getPostUrl(clickedElement) {
		var pid = parseInt(clickedElement.parents('[data-pid]').attr('data-pid'), 10);
		return '/post' + (pid ? '/' + (pid) : '');
	}
 
	return module;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/sort.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/sort.js

Statements: 6.67% (1 / 15)      Branches: 0% (0 / 2)      Functions: 0% (0 / 4)      Lines: 6.67% (1 / 15)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28      2                                                
'use strict';
/* globals define, config, socket, app, ajaxify, templates */
 
define('sort', ['components'], function (components) {
	var module = {};
 
	module.handleSort = function (field, method, gotoOnSave) {
		var threadSort = components.get('thread/sort');
		threadSort.find('i').removeClass('fa-check');
		var currentSetting = threadSort.find('a[data-sort="' + config[field] + '"]');
		currentSetting.find('i').addClass('fa-check');
 
		$('.category, .topic').on('click', '[component="thread/sort"] a', function () {
			var newSetting = $(this).attr('data-sort');
			socket.emit(method, newSetting, function (err) {
				if (err) {
					return app.alertError(err.message);
				}
				config[field] = newSetting;
				ajaxify.go(gotoOnSave);
			});
		});
	};
 
	return module;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/sounds.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/sounds.js

Statements: 14.58% (7 / 48)      Branches: 0% (0 / 22)      Functions: 0% (0 / 13)      Lines: 14.58% (7 / 48)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92      2                                       1                     1       1 1                                   1                               1                                    
"use strict";
/* global app, define, socket, config */
 
define('sounds', ['buzz'], function (buzz) {
	var	Sounds = {};
 
	var loadedSounds = {};
	var eventSoundMapping;
	var files;
 
	socket.on('event:sounds.reloadMapping', function () {
		Sounds.reloadMapping();
	});
 
	Sounds.reloadMapping = function () {
		socket.emit('modules.sounds.getMapping', function (err, mapping) {
			if (err) {
				return app.alertError(err.message);
			}
			eventSoundMapping = mapping;
		});
	};
 
	function loadData(callback) {
		socket.emit('modules.sounds.getData', function (err, data) {
			if (err) {
				return app.alertError('[sounds] Could not load sound mapping!');
			}
			eventSoundMapping = data.mapping;
			files = data.files;
			callback();
		});
	}
 
	function isSoundLoaded(fileName) {
		return loadedSounds[fileName];
	}
 
	function loadFile(fileName, callback) {
		function createSound() {
			if (files && files[fileName]) {
				loadedSounds[fileName] = new buzz.sound(files[fileName]);
			}
			callback();
		}
 
		if (isSoundLoaded(fileName)) {
			return callback();
		}
 
		if (!files || !files[fileName]) {
			return loadData(createSound);
		}
		createSound();
	}
 
	Sounds.play = function (name) {
		function play() {
			Sounds.playFile(eventSoundMapping[name]);
		}
 
		if (!eventSoundMapping) {
			return loadData(play);
		}
 
		play();
	};
 
	Sounds.playFile = function (fileName) {
		if (!fileName) {
			return;
		}
 
		function play() {
			if (loadedSounds[fileName]) {
				loadedSounds[fileName].play();
			} else {
				app.alertError('[sounds] Not found: ' + fileName);
			}
		}
 
		if (isSoundLoaded(fileName)) {
			play();
		} else {
			loadFile(fileName, play);
		}
	};
 
	return Sounds;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/string.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/string.js

Statements: 14.93% (30 / 201)      Branches: 4.57% (9 / 197)      Functions: 5.33% (4 / 75)      Lines: 100% (1 / 1)      Ignored: none     

1 2 3 4 5    87    
/*
string.js - Copyright (C) 2012-2013, JP Richardson <jprichardson@gmail.com>
*/!function(){"use strict";function n(e,t){t!==null&&t!==undefined?typeof t=="string"?e.s=t:e.s=t.toString():e.s=t,e.orig=t,t!==null&&t!==undefined?e.__defineGetter__?e.__defineGetter__("length",function(){return e.s.length}):e.length=t.length:e.length=-1}function r(e){n(this,e)}function u(){for(var e in s)(function(e){var t=s[e];i.hasOwnProperty(e)||(o.push(e),i[e]=function(){return String.prototype.s=this,t.apply(this,arguments)})})(e)}function a(){for(var e=0;e<o.length;++e)delete String.prototype[o[e]];o.length=0}function c(){var e=h(),t={};for(var n=0;n<e.length;++n){var r=e[n],s=i[r];try{var o=typeof s.apply("teststring",[]);t[r]=o}catch(u){}}return t}function h(){var e=[];Eif(Object.getOwnPropertyNames)return e=Object.getOwnPropertyNames(i),e.splice(e.indexOf("valueOf"),1),e.splice(e.indexOf("toString"),1),e;var t={},n=[];for(var r in String.prototype)t[r]=r;for(var r in Object.prototype)delete t[r];for(var r in t)e.push(r);return e}function p(e){return new r(e)}function d(e,t){var n=[],r;for(r=0;r<e.length;r++)n.push(e[r]),t&&t.call(e,e[r],r);return n}var e="1.7.0",t={},i=String.prototype,s=r.prototype={between:function(e,t){var n=this.s,r=n.indexOf(e),i=n.indexOf(t),s=r+e.length;return new this.constructor(i>r?n.slice(s,i):"")},camelize:function(){var e=this.trim().s.replace(/(\-|_|\s)+(.)?/g,function(e,t,n){return n?n.toUpperCase():""});return new this.constructor(e)},capitalize:function(){return new this.constructor(this.s.substr(0,1).toUpperCase()+this.s.substring(1).toLowerCase())},charAt:function(e){return this.s.charAt(e)},chompLeft:function(e){var t=this.s;return t.indexOf(e)===0?(t=t.slice(e.length),new this.constructor(t)):this},chompRight:function(e){if(this.endsWith(e)){var t=this.s;return t=t.slice(0,t.length-e.length),new this.constructor(t)}return this},collapseWhitespace:function(){var e=this.s.replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"");return new this.constructor(e)},contains:function(e){return this.s.indexOf(e)>=0},count:function(e){var t=0,n=this.s.indexOf(e);while(n>=0)t+=1,n=this.s.indexOf(e,n+1);return t},dasherize:function(){var e=this.trim().s.replace(/[_\s]+/g,"-").replace(/([A-Z])/g,"-$1").replace(/-+/g,"-").toLowerCase();return new this.constructor(e)},decodeHtmlEntities:function(){var e=this.s;return e=e.replace(/&#(\d+);?/g,function(e,t){return String.fromCharCode(t)}).replace(/&#[xX]([A-Fa-f0-9]+);?/g,function(e,t){return String.fromCharCode(parseInt(t,16))}).replace(/&([^;\W]+;?)/g,function(e,n){var r=n.replace(/;$/,""),i=t[n]||n.match(/;$/)&&t[r];return typeof i=="number"?String.fromCharCode(i):typeof i=="string"?i:e}),new this.constructor(e)},endsWith:function(e){var t=this.s.length-e.length;return t>=0&&this.s.indexOf(e,t)===t},escapeHTML:function(){return new this.constructor(this.s.replace(/[&<>"']/g,function(e){return"&"+m[e]+";"}))},ensureLeft:function(e){var t=this.s;return t.indexOf(e)===0?this:new this.constructor(e+t)},ensureRight:function(e){var t=this.s;return this.endsWith(e)?this:new this.constructor(t+e)},humanize:function(){if(this.s===null||this.s===undefined)return new this.constructor("");var e=this.underscore().replace(/_id$/,"").replace(/_/g," ").trim().capitalize();return new this.constructor(e)},isAlpha:function(){return!/[^a-z\xC0-\xFF]/.test(this.s.toLowerCase())},isAlphaNumeric:function(){return!/[^0-9a-z\xC0-\xFF]/.test(this.s.toLowerCase())},isEmpty:function(){return this.s===null||this.s===undefined?!0:/^[\s\xa0]*$/.test(this.s)},isLower:function(){return this.isAlpha()&&this.s.toLowerCase()===this.s},isNumeric:function(){return!/[^0-9]/.test(this.s)},isUpper:function(){return this.isAlpha()&&this.s.toUpperCase()===this.s},left:function(e){if(e>=0){var t=this.s.substr(0,e);return new this.constructor(t)}return this.right(-e)},lines:function(){return this.replaceAll("\r\n","\n").s.split("\n")},pad:function(e,t){t==null&&(t=" ");if(this.s.length>=e)return new this.constructor(this.s);e-=this.s.length;var n=Array(Math.ceil(e/2)+1).join(t),r=Array(Math.floor(e/2)+1).join(t);return new this.constructor(n+this.s+r)},padLeft:function(e,t){return t==null&&(t=" "),this.s.length>=e?new this.constructor(this.s):new this.constructor(Array(e-this.s.length+1).join(t)+this.s)},padRight:function(e,t){return t==null&&(t=" "),this.s.length>=e?new this.constructor(this.s):new this.constructor(this.s+Array(e-this.s.length+1).join(t))},parseCSV:function(e,t,n,r){e=e||",",n=n||"\\",typeof t=="undefined"&&(t='"');var i=0,s=[],o=[],u=this.s.length,a=!1,f=this,l=function(e){return f.s.charAt(e)};if(typeof r!="undefined")var c=[];t||(a=!0);while(i<u){var h=l(i);switch(h){case n:if(a&&(n!==t||l(i+1)===t)){i+=1,s.push(l(i));break}if(n!==t)break;case t:a=!a;break;case e:a&&t?s.push(h):(o.push(s.join("")),s.length=0);break;case r:a?s.push(h):c&&(o.push(s.join("")),c.push(o),o=[],s.length=0);break;default:a&&s.push(h)}i+=1}return o.push(s.join("")),c?(c.push(o),c):o},replaceAll:function(e,t){var n=this.s.split(e).join(t);return new this.constructor(n)},right:function(e){if(e>=0){var t=this.s.substr(this.s.length-e,e);return new this.constructor(t)}return this.left(-e)},setValue:function(e){return n(this,e),this},slugify:function(){var e=(new r(this.s.replace(/[^\w\s-]/g,"").toLowerCase())).dasherize().s;return e.charAt(0)==="-"&&(e=e.substr(1)),new this.constructor(e)},startsWith:function(e){return this.s.lastIndexOf(e,0)===0},stripPunctuation:function(){return new this.constructor(this.s.replace(/[^\w\s]|_/g,"").replace(/\s+/g," "))},stripTags:function(){var e=this.s,t=arguments.length>0?arguments:[""];return d(t,function(t){e=e.replace(RegExp("</?"+t+"[^<>]*>","gi"),"")}),new this.constructor(e)},template:function(e,t,n){var r=this.s,t=t||p.TMPL_OPEN,n=n||p.TMPL_CLOSE,i=t.replace(/[-[\]()*\s]/g,"\\$&").replace(/\$/g,"\\$"),s=n.replace(/[-[\]()*\s]/g,"\\$&").replace(/\$/g,"\\$"),o=new RegExp(i+"(.+?)"+s,"g"),u=r.match(o)||[];return u.forEach(function(i){var s=i.substring(t.length,i.length-n.length);typeof e[s]!="undefined"&&(r=r.replace(i,e[s]))}),new this.constructor(r)},times:function(e){return new this.constructor((new Array(e+1)).join(this.s))},toBoolean:function(){if(typeof this.orig=="string"){var e=this.s.toLowerCase();return e==="true"||e==="yes"||e==="on"}return this.orig===!0||this.orig===1},toFloat:function(e){var t=parseFloat(this.s);return e?parseFloat(t.toFixed(e)):t},toInt:function(){return/^\s*-?0x/i.test(this.s)?parseInt(this.s,16):parseInt(this.s,10)},trim:function(){var e;return typeof i.trim=="undefined"?e=this.s.replace(/(^\s*|\s*$)/g,""):e=this.s.trim(),new this.constructor(e)},trimLeft:function(){var e;return i.trimLeft?e=this.s.trimLeft():e=this.s.replace(/(^\s*)/g,""),new this.constructor(e)},trimRight:function(){var e;return i.trimRight?e=this.s.trimRight():e=this.s.replace(/\s+$/,""),new this.constructor(e)},truncate:function(e,t){var n=this.s;e=~~e,t=t||"...";if(n.length<=e)return new this.constructor(n);var i=function(e){return e.toUpperCase()!==e.toLowerCase()?"A":" "},s=n.slice(0,e+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,""):s=(new r(s.slice(0,s.length-1))).trimRight().s,(s+t).length>n.length?new r(n):new r(n.slice(0,s.length)+t)},toCSV:function(){function u(e){return e!==null&&e!==""}var e=",",t='"',n="\\",i=!0,s=!1,o=[];typeof arguments[0]=="object"?(e=arguments[0].delimiter||e,e=arguments[0].separator||e,t=arguments[0].qualifier||t,i=!!arguments[0].encloseNumbers,n=arguments[0].escape||n,s=!!arguments[0].keys):typeof arguments[0]=="string"&&(e=arguments[0]),typeof arguments[1]=="string"&&(t=arguments[1]),arguments[1]===null&&(t=null);if(this.orig instanceof Array)o=this.orig;else for(var a in this.orig)this.orig.hasOwnProperty(a)&&(s?o.push(a):o.push(this.orig[a]));var f=n+t,l=[];for(var c=0;c<o.length;++c){var h=u(t);typeof o[c]=="number"&&(h&=i),h&&l.push(t);if(o[c]!==null&&o[c]!==undefined){var p=(new r(o[c])).replaceAll(t,f).s;l.push(p)}else l.push("");h&&l.push(t),e&&l.push(e)}return l.length=l.length-1,new this.constructor(l.join(""))},toString:function(){return this.s},underscore:function(){var e=this.trim().s.replace(/([a-z\d])([A-Z]+)/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase();return(new r(this.s.charAt(0))).isUpper()&&(e="_"+e),new this.constructor(e)},unescapeHTML:function(){return new this.constructor(this.s.replace(/\&([^;]+);/g,function(e,t){var n;return t in v?v[t]:(n=t.match(/^#x([\da-fA-F]+)$/))?String.fromCharCode(parseInt(n[1],16)):(n=t.match(/^#(\d+)$/))?String.fromCharCode(~~n[1]):e}))},valueOf:function(){return this.s.valueOf()}},o=[],f=c();for(var l in f)(function(e){var t=i[e];typeof t=="function"&&(s[e]||(f[e]==="string"?s[e]=function(){return new this.constructor(t.apply(this,arguments))}:s[e]=t))})(l);s.repeat=s.times,s.include=s.contains,s.toInteger=s.toInt,s.toBool=s.toBoolean,s.decodeHTMLEntities=s.decodeHtmlEntities,s.constructor=r,p.extendPrototype=u,p.restorePrototype=a,p.VERSION=e,p.TMPL_OPEN="{{",p.TMPL_CLOSE="}}",p.ENTITIES=t,typeof module!="undefined"&&typeof module.exports!="undefined"?module.exports=p:typeof define=="function"&&define.amd?define('string', [],function(){return p}):window.S=p;var v={lt:"<",gt:">",quot:'"',apos:"'",amp:"&"},m={};for(var g in v)m[v[g]]=g;t={amp:"&",gt:">",lt:"<",quot:'"',apos:"'",AElig:198,Aacute:193,Acirc:194,Agrave:192,Aring:197,Atilde:195,Auml:196,Ccedil:199,ETH:208,Eacute:201,Ecirc:202,Egrave:200,Euml:203,Iacute:205,Icirc:206,Igrave:204,Iuml:207,Ntilde:209,Oacute:211,Ocirc:212,Ograve:210,Oslash:216,Otilde:213,Ouml:214,THORN:222,Uacute:218,Ucirc:219,Ugrave:217,Uuml:220,Yacute:221,aacute:225,acirc:226,aelig:230,agrave:224,aring:229,atilde:227,auml:228,ccedil:231,eacute:233,ecirc:234,egrave:232,eth:240,euml:235,iacute:237,icirc:238,igrave:236,iuml:239,ntilde:241,oacute:243,ocirc:244,ograve:242,oslash:248,otilde:245,ouml:246,szlig:223,thorn:254,uacute:250,ucirc:251,ugrave:249,uuml:252,yacute:253,yuml:255,copy:169,reg:174,nbsp:160,iexcl:161,cent:162,pound:163,curren:164,yen:165,brvbar:166,sect:167,uml:168,ordf:170,laquo:171,not:172,shy:173,macr:175,deg:176,plusmn:177,sup1:185,sup2:178,sup3:179,acute:180,micro:181,para:182,middot:183,cedil:184,ordm:186,raquo:187,frac14:188,frac12:189,frac34:190,iquest:191,times:215,divide:247,"OElig;":338,"oelig;":339,"Scaron;":352,"scaron;":353,"Yuml;":376,"fnof;":402,"circ;":710,"tilde;":732,"Alpha;":913,"Beta;":914,"Gamma;":915,"Delta;":916,"Epsilon;":917,"Zeta;":918,"Eta;":919,"Theta;":920,"Iota;":921,"Kappa;":922,"Lambda;":923,"Mu;":924,"Nu;":925,"Xi;":926,"Omicron;":927,"Pi;":928,"Rho;":929,"Sigma;":931,"Tau;":932,"Upsilon;":933,"Phi;":934,"Chi;":935,"Psi;":936,"Omega;":937,"alpha;":945,"beta;":946,"gamma;":947,"delta;":948,"epsilon;":949,"zeta;":950,"eta;":951,"theta;":952,"iota;":953,"kappa;":954,"lambda;":955,"mu;":956,"nu;":957,"xi;":958,"omicron;":959,"pi;":960,"rho;":961,"sigmaf;":962,"sigma;":963,"tau;":964,"upsilon;":965,"phi;":966,"chi;":967,"psi;":968,"omega;":969,"thetasym;":977,"upsih;":978,"piv;":982,"ensp;":8194,"emsp;":8195,"thinsp;":8201,"zwnj;":8204,"zwj;":8205,"lrm;":8206,"rlm;":8207,"ndash;":8211,"mdash;":8212,"lsquo;":8216,"rsquo;":8217,"sbquo;":8218,"ldquo;":8220,"rdquo;":8221,"bdquo;":8222,"dagger;":8224,"Dagger;":8225,"bull;":8226,"hellip;":8230,"permil;":8240,"prime;":8242,"Prime;":8243,"lsaquo;":8249,"rsaquo;":8250,"oline;":8254,"frasl;":8260,"euro;":8364,"image;":8465,"weierp;":8472,"real;":8476,"trade;":8482,"alefsym;":8501,"larr;":8592,"uarr;":8593,"rarr;":8594,"darr;":8595,"harr;":8596,"crarr;":8629,"lArr;":8656,"uArr;":8657,"rArr;":8658,"dArr;":8659,"hArr;":8660,"forall;":8704,"part;":8706,"exist;":8707,"empty;":8709,"nabla;":8711,"isin;":8712,"notin;":8713,"ni;":8715,"prod;":8719,"sum;":8721,"minus;":8722,"lowast;":8727,"radic;":8730,"prop;":8733,"infin;":8734,"ang;":8736,"and;":8743,"or;":8744,"cap;":8745,"cup;":8746,"int;":8747,"there4;":8756,"sim;":8764,"cong;":8773,"asymp;":8776,"ne;":8800,"equiv;":8801,"le;":8804,"ge;":8805,"sub;":8834,"sup;":8835,"nsub;":8836,"sube;":8838,"supe;":8839,"oplus;":8853,"otimes;":8855,"perp;":8869,"sdot;":8901,"lceil;":8968,"rceil;":8969,"lfloor;":8970,"rfloor;":8971,"lang;":9001,"rang;":9002,"loz;":9674,"spades;":9824,"clubs;":9827,"hearts;":9829,"diams;":9830}}.call(this);
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/taskbar.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/taskbar.js

Statements: 5.97% (4 / 67)      Branches: 0% (0 / 22)      Functions: 0% (0 / 16)      Lines: 5.97% (4 / 67)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142      2                                                                                                                                                                                       1                   1       1                                                                
"use strict";
/*global define, app, templates*/
 
define('taskbar', function () {
	var taskbar = {};
 
	taskbar.init = function () {
		var self = this;
 
		templates.parse('modules/taskbar', {}, function (html) {
			self.taskbar = $(html);
			self.tasklist = self.taskbar.find('ul');
			$(document.body).append(self.taskbar);
 
			self.taskbar.on('click', 'li', function () {
				var	$btn = $(this),
					module = $btn.attr('data-module'),
					uuid = $btn.attr('data-uuid');
 
				require([module], function (module) {
					if (!$btn.hasClass('active')) {
						minimizeAll();
						module.load(uuid);
						taskbar.toggleNew(uuid, false);
						app.alternatingTitle('');
 
						taskbar.tasklist.removeClass('active');
						$btn.addClass('active');
					} else {
						module.minimize(uuid);
					}
				});
 
				return false;
			});
		});
	};
 
	taskbar.discard = function (module, uuid) {
		var btnEl = taskbar.tasklist.find('[data-module="' + module + '"][data-uuid="' + uuid + '"]');
		btnEl.remove();
		
		update();
	};
 
	taskbar.push = function (module, uuid, options) {
		var element = taskbar.tasklist.find('li[data-uuid="' + uuid + '"]');
 
		var data = {
			module: module,
			uuid: uuid,
			options: options,
			element: element
		};
 
		$(window).trigger('filter:taskbar.push', data);
 
		if (!element.length && data.module) {
			createTaskbar(data);
		}
	};
 
	taskbar.get = function (module) {
		var items = $('[data-module="' + module + '"]').map(function (idx, el) {
			return $(el).data();
		});
 
		return items;
	};
 
	taskbar.minimize = function (module, uuid) {
		var btnEl = taskbar.tasklist.find('[data-module="' + module + '"][data-uuid="' + uuid + '"]');
		btnEl.toggleClass('active', false);
	};
 
	taskbar.toggleNew = function (uuid, state, silent) {
		var btnEl = taskbar.tasklist.find('[data-uuid="' + uuid + '"]');
		btnEl.toggleClass('new', state);
 
		if (!silent) {
			$(window).trigger('action:taskbar.toggleNew', uuid);
		}
	};
 
	taskbar.updateActive = function (uuid) {
		var	tasks = taskbar.tasklist.find('li');
		tasks.removeClass('active');
		tasks.filter('[data-uuid="' + uuid + '"]').addClass('active');
	};
 
	taskbar.isActive = function (uuid) {
		var taskBtn = taskbar.tasklist.find('li[data-uuid="' + uuid + '"]');
		return taskBtn.hasClass('active');
	};
 
	function update() {
		var	tasks = taskbar.tasklist.find('li');
 
		if (tasks.length > 0) {
			taskbar.taskbar.attr('data-active', '1');
		} else {
			taskbar.taskbar.removeAttr('data-active');
		}
	}
 
	function minimizeAll() {
		taskbar.tasklist.find('.active').removeClass('active');
	}
 
	function createTaskbar(data) {
		var title = $('<div></div>').text(data.options.title || 'NodeBB Task').html();
 
		var	taskbarEl = $('<li />')
			.addClass(data.options.className)
			.html('<a href="#">' +
				(data.options.icon ? '<i class="fa ' + data.options.icon + '"></i> ' : '') +
				(data.options.image ? '<img src="' + data.options.image + '"/> ' : '') +
				'<span>' + title + '</span>' +
				'</a>')
			.attr({
				'data-module': data.module,
				'data-uuid': data.uuid
			})
			.addClass(data.options.state !== undefined ? data.options.state : 'active');
 
		if (!data.options.state || data.options.state === 'active') {
			minimizeAll();
		}
 
		taskbar.tasklist.append(taskbarEl);
		update();
 
		data.element = taskbarEl;
 
		taskbarEl.data(data);
		$(window).trigger('action:taskbar.pushed', data);
	}
 
	return taskbar;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/topicSelect.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/topicSelect.js

Statements: 10.2% (5 / 49)      Branches: 0% (0 / 8)      Functions: 0% (0 / 11)      Lines: 10.2% (5 / 49)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86        2                                                           1                                     1                         1                         1            
'use strict';
 
/* globals define*/
 
define('topicSelect', ['components'], function (components) {
	var TopicSelect = {};
	var lastSelected;
 
	var topicsContainer;
 
	TopicSelect.init = function (onSelect) {
		topicsContainer = $('[component="category"]');
		topicsContainer.on('selectstart', function () {
			return false;
		});
 
		topicsContainer.on('click', '[component="topic/select"]', function (ev) {
			var select = $(this);
 
			if (ev.shiftKey) {
				selectRange($(this).parents('[component="category/topic"]').attr('data-tid'));
				lastSelected = select;
				return false;
			}
 
			var isSelected = select.parents('[data-tid]').hasClass('selected');
			toggleSelect(select, !isSelected);
			lastSelected = select;
			if (typeof onSelect === 'function') {
				onSelect();
			}
		});
	};
 
	function toggleSelect(select, isSelected) {
		select.toggleClass('fa-check-square-o', isSelected);
		select.toggleClass('fa-square-o', !isSelected);
		select.parents('[component="category/topic"]').toggleClass('selected', isSelected);
	}
 
	TopicSelect.getSelectedTids = function () {
		var tids = [];
		topicsContainer.find('[component="category/topic"].selected').each(function () {
			tids.push($(this).attr('data-tid'));
		});
		return tids;
	};
 
	TopicSelect.unselectAll = function () {
		topicsContainer.find('[component="category/topic"].selected').removeClass('selected');
		topicsContainer.find('[component="topic/select"]').toggleClass('fa-check-square-o', false).toggleClass('fa-square-o', true);
	};
 
	function selectRange(clickedTid) {
 
		if(!lastSelected) {
			lastSelected = $('[component="category/topic"]').first().find('[component="topic/select"]');
		}
 
		var isClickedSelected = components.get('category/topic', 'tid', clickedTid).hasClass('selected');
 
		var clickedIndex = getIndex(clickedTid);
		var lastIndex = getIndex(lastSelected.parents('[component="category/topic"]').attr('data-tid'));
		selectIndexRange(clickedIndex, lastIndex, !isClickedSelected);
	}
 
	function selectIndexRange(start, end, isSelected) {
		if (start > end) {
			var tmp = start;
			start = end;
			end = tmp;
		}
 
		for(var i = start; i <= end; ++i) {
			var topic = $('[component="category/topic"]').eq(i);
			toggleSelect(topic.find('[component="topic/select"]'), isSelected);
		}
	}
 
	function getIndex(tid) {
		return components.get('category/topic', 'tid', tid).index('[component="category/topic"]');
	}
 
	return TopicSelect;
});
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/translator.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/translator.js

Statements: 5.16% (11 / 213)      Branches: 4.31% (5 / 116)      Functions: 4.65% (2 / 43)      Lines: 5.21% (11 / 211)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485    2   1     2         2   2 2 2   1                                       1             1                                                                                               1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
/* global define, jQuery, config, utils, window, Promise */
 
(function (factory) {
	'use strict';
	function loadClient(language, namespace) {
		return Promise.resolve(jQuery.getJSON(config.relative_path + '/api/language/' + language + '/' + namespace));
	}
	Iif (typeof define === 'function' && define.amd) {
		// AMD. Register as a named module
		define('translator', ['string'], function (string) {
			return factory(string, loadClient);
		});
	} else Eif (typeof module === 'object' && module.exports) {
		// Node
		(function () {
			require('promise-polyfill');
			var languages = require('../../../src/languages');
 
			function loadServer(language, namespace) {
				return new Promise(function (resolve, reject) {
					languages.get(language, namespace, function (err, data) {
						if (err) {
							reject(err);
						} else {
							resolve(data);
						}
					});
				});
			}
 
			module.exports = factory(require('string'), loadServer);
		}());
	} else {
		window.translator = factory(window.string, loadClient);
	}
}(function (string, load) {
	'use strict';
	var assign = Object.assign || jQuery.extend;
	function classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
	var Translator = (function () {
		/**
		 * Construct a new Translator object
		 * @param {string} language - Language code for this translator instance
		 */
		function Translator(language) {
			var self = this;
			classCallCheck(self, Translator);
 
			if (!language) {
				throw new TypeError('Parameter `language` must be a language string. Received ' + language + (language === '' ? '(empty string)' : ''));
			}
 
			self.modules = Object.keys(Translator.moduleFactories).map(function (namespace) {
				var factory = Translator.moduleFactories[namespace];
				return [namespace, factory(language)];
			}).reduce(function (prev, elem) {
				var namespace = elem[0];
				var module = elem[1];
				prev[namespace] = module;
 
				return prev;
			}, {});
 
			self.lang = language;
			self.translations = {};
		}
 
		Translator.prototype.load = load;
 
		/**
		 * Parse the translation instructions into the language of the Translator instance
		 * @param {string} str - Source string
		 * @returns {Promise<string>}
		 */
		Translator.prototype.translate = function translate(str) {
			// regex for valid text in namespace / key
			var validText = 'a-zA-Z0-9\\-_.\\/';
			var validTextRegex = new RegExp('[' + validText + ']');
			var invalidTextRegex = new RegExp('[^' + validText + '\\]]');
 
			// current cursor position
			var cursor = 0;
			// last break of the input string
			var lastBreak = 0;
			// length of the input string
			var len = str.length;
			// array to hold the promises for the translations
			// and the strings of untranslated text in between
			var toTranslate = [];
 
			// split a translator string into an array of tokens
			// but don't split by commas inside other translator strings
			function split(text) {
				var len = text.length;
				var arr = [];
				var i = 0;
				var brk = 0;
				var level = 0;
 
				while (i + 2 <= len) {
					if (text.slice(i, i + 2) === '[[') {
						level += 1;
						i += 1;
					} else if (text.slice(i, i + 2) === ']]') {
						level -= 1;
						i += 1;
					} else if (level === 0 && text[i] === ',') {
						arr.push(text.slice(brk, i).trim());
						i += 1;
						brk = i;
					}
					i += 1;
				}
				arr.push(text.slice(brk, i + 1).trim());
				return arr;
			}
 
			// the loooop, we'll go to where the cursor
			// is equal to the length of the string since
			// slice doesn't include the ending index
			while (cursor + 2 <= len) {
				// if the current position in the string looks
				// like the beginning of a translation string
				if (str.slice(cursor, cursor + 2) === '[[') {
					// split the string from the last break
					// to the character before the cursor
					// add that to the result array
					toTranslate.push(str.slice(lastBreak, cursor));
					// set the cursor position past the beginning
					// brackets of the translation string
					cursor += 2;
					// set the last break to our current
					// spot since we just broke the string
					lastBreak = cursor;
 
					// the current level of nesting of the translation strings
					var level = 0;
					var sliced;
					// validating the current string is actually a translation
					var textBeforeColonFound = false;
					var colonFound = false;
					var textAfterColonFound = false;
					var commaAfterNameFound = false;
 
					while (cursor + 2 <= len) {
						sliced = str.slice(cursor, cursor + 2);
						// found some text after the double bracket, 
						// so this is probably a translation string
						if (!textBeforeColonFound && validTextRegex.test(sliced[0])) {
							textBeforeColonFound = true;
							cursor += 1;
						// found a colon, so this is probably a translation string
						} else if (textBeforeColonFound && !colonFound && sliced[0] === ':') {
							colonFound = true;
							cursor += 1;
						// found some text after the colon,
						// so this is probably a translation string
						} else if (colonFound && !textAfterColonFound && validTextRegex.test(sliced[0])) {
							textAfterColonFound = true;
							cursor += 1;
						} else if (textAfterColonFound && !commaAfterNameFound && sliced[0] === ',') {
							commaAfterNameFound = true;
							cursor += 1;
						// a space or comma was found before the name
						// this isn't a translation string, so back out
						} else if (!(textBeforeColonFound && colonFound && textAfterColonFound && commaAfterNameFound) && 
								invalidTextRegex.test(sliced[0])) {
							cursor += 1;
							lastBreak -= 2;
							if (level > 0) {
								level -= 1;
							} else {
								break;
							}
						// if we're at the beginning of another translation string,
						// we're nested, so add to our level
						} else if (sliced === '[[') {
							level += 1;
							cursor += 2;
						// if we're at the end of a translation string
						} else if (sliced === ']]') {
							// if we're at the base level, then this is the end
							if (level === 0) {
								// so grab the name and args
								var result = split(str.slice(lastBreak, cursor));
								var name = result[0];
								var args = result.slice(1);
 
								// add the translation promise to the array
								toTranslate.push(this.translateKey(name, args));
								// skip past the ending brackets
								cursor += 2;
								// set this as our last break
								lastBreak = cursor;
								// and we're no longer in a translation string,
								// so continue with the main loop
								break;
							}
							// otherwise we lower the level
							level -= 1;
							// and skip past the ending brackets
							cursor += 2;
						} else {
							// otherwise just move to the next character
							cursor += 1;
						}
					}
				}
				// move to the next character
				cursor += 1;
			}
 
			// add the remaining text after the last translation string
			toTranslate.push(str.slice(lastBreak, cursor + 2));
 
			// and return a promise for the concatenated translated string
			return Promise.all(toTranslate).then(function (translated) {
				return translated.join('');
			});
		};
 
		/**
		 * Translates a specific key and array of arguments
		 * @param {string} name - Translation key (ex. 'global:home')
		 * @param {string[]} args - Arguments for `%1`, `%2`, etc
		 * @returns {Promise<string>}
		 */
		Translator.prototype.translateKey = function translateKey(name, args) {
			var self = this;
 
			var result = name.split(':', 2);
			var namespace = result[0];
			var key = result[1];
 
			if (self.modules[namespace]) {
				return Promise.resolve(self.modules[namespace](key, args));
			}
 
			if (namespace && !key) {
				return Promise.resolve('[[' + namespace + ']]');
			}
 
			var translation = this.getTranslation(namespace, key);
			var argsToTranslate = args.map(function (arg) {
				return string(arg).collapseWhitespace().decodeHTMLEntities().escapeHTML().s;
			}).map(function (arg) {
				return self.translate(arg);
			});
 
			// so we can await all promises at once
			argsToTranslate.unshift(translation);
 
			return Promise.all(argsToTranslate).then(function (result) {
				var translated = result[0];
				var translatedArgs = result.slice(1);
 
				if (!translated) {
					return key;
				}
				var out = translated;
				translatedArgs.forEach(function (arg, i) {
					out = out.replace(new RegExp('%' + (i + 1), 'g'), arg);
				});
				return out;
			});
		};
 
		/**
		 * Load translation file (or use a cached version), and optionally return the translation of a certain key
		 * @param {string} namespace - The file name of the translation namespace
		 * @param {string} [key] - The key of the specific translation to getJSON
		 * @returns {Promise<Object|string>}
		 */
		Translator.prototype.getTranslation = function getTranslation(namespace, key) {
			var translation;
			if (!namespace) {
				console.warn('[translator] Parameter `namespace` is ' + namespace + (namespace === '' ? '(empty string)' : ''));
				translation = Promise.resolve({});
			} else {
				translation = this.translations[namespace] = this.translations[namespace] || this.load(this.lang, namespace);
			}
 
			if (key) {
				return translation.then(function (x) {
					return x[key];
				});
			}
			return translation;
		};
 
		/**
		 * Get the language of the current environment, falling back to defaults
		 * @returns {string}
		 */
		Translator.getLanguage = function getLanguage() {
			var lang;
 
			if (typeof window === 'object' && window.config && window.utils) {
				lang = utils.params().lang || config.userLang || config.defaultLang || 'en-GB';
			} else {
				var meta = require('../../../src/meta');
				lang = meta.config.defaultLang || 'en-GB';
			}
 
			return lang;
		};
 
		/**
		 * Create and cache a new Translator instance, or return a cached one
		 * @param {string} [language] - ('en-GB') Language string
		 * @returns {Translator}
		 */
		Translator.create = function create(language) {
			if (!language) {
				language = Translator.getLanguage();
			}
 
			Translator.cache[language] = Translator.cache[language] || new Translator(language);
 
			return Translator.cache[language];
		};
 
		Translator.cache = {};
 
		/**
		 * Register a custom module to handle translations
		 * @param {string} namespace - Namespace to handle translation for
		 * @param {Function} factory - Function to return the translation function for this namespace
		 */
		Translator.registerModule = function registerModule(namespace, factory) {
			Translator.moduleFactories[namespace] = factory;
 
			Object.keys(Translator.cache).forEach(function (key) {
				var translator = Translator.cache[key];
				translator.modules[namespace] = factory(translator.lang);
			});
		};
 
		Translator.moduleFactories = {};
 
		return Translator;
	}());
 
	var adaptor = {
		/**
		 * The Translator class
		 */
		Translator: Translator,
 
		/**
		 * Legacy translator function for backwards compatibility
		 */
		translate: function translate(text, language, callback) {
			// console.warn('[translator] `translator.translate(text, [lang, ]callback)` is deprecated. ' +
			//   'Use the `translator.Translator` class instead.');
 
			var cb = callback;
			var lang = language;
			if (typeof language === 'function') {
				cb = language;
				lang = null;
			}
 
			if (!(typeof text === 'string' || text instanceof String) || text === '') {
				return cb('');
			}
 
			Translator.create(lang).translate(text).then(function (output) {
				return cb(output);
			}).catch(function (err) {
				console.error('Translation failed: ' + err.stack);
			});
		},
 
		/**
		 * Construct a translator pattern
		 * @param {string} name - Translation name
		 * @param {string[]} args - Optional arguments for the pattern
		 */
		compile: function compile() {
			var args = Array.prototype.slice.call(arguments, 0);
 
			return '[[' + args.join(', ') + ']]';
		},
 
		/**
		 * Escape translation patterns from text
		 */
		escape: function escape(text) {
			return typeof text === 'string' ? text.replace(/\[\[([\S]*?)\]\]/g, '\\[\\[$1\\]\\]') : text;
		},
 
		/**
		 * Unescape translation patterns from text
		 */
		unescape: function unescape(text) {
			return typeof text === 'string' ? text.replace(/\\\[\\\[([\S]*?)\\\]\\\]/g, '[[$1]]') : text;
		},
 
		/**
		 * Add translations to the cache
		 */
		addTranslation: function addTranslation(language, namespace, translation) {
			Translator.create(language).getTranslation(namespace).then(function (translations) {
				assign(translations, translation);
			});
		},
 
		/**
		 * Get the translations object
		 */
		getTranslations: function getTranslations(language, namespace, callback) {
			callback = callback || function () {};
			Translator.create(language).getTranslation(namespace).then(callback);
		},
 
		/**
		 * Alias of getTranslations
		 */
		load: function load(language, namespace, callback) {
			adaptor.getTranslations(language, namespace, callback);
		},
 
		/**
		 * Get the language of the current environment, falling back to defaults
		 */
		getLanguage: Translator.getLanguage,
 
		toggleTimeagoShorthand: function toggleTimeagoShorthand() {
			var tmp = assign({}, jQuery.timeago.settings.strings);
			jQuery.timeago.settings.strings = assign({}, adaptor.timeagoShort);
			adaptor.timeagoShort = assign({}, tmp);
		},
		prepareDOM: function prepareDOM() {
			// Load the appropriate timeago locale file,
			// and correct NodeBB language codes to timeago codes, if necessary
			var languageCode = void 0;
			switch (config.userLang) {
				case 'en-GB':
				case 'en-US':
					languageCode = 'en';
					break;
 
				case 'fa-IR':
					languageCode = 'fa';
					break;
 
				case 'pt-BR':
					languageCode = 'pt-br';
					break;
 
				case 'nb':
					languageCode = 'no';
					break;
 
				default:
					languageCode = config.userLang;
					break;
			}
 
			jQuery.getScript(config.relative_path + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').done(function () {
				jQuery('.timeago').timeago();
				adaptor.timeagoShort = assign({}, jQuery.timeago.settings.strings);
 
				// Retrieve the shorthand timeago values as well
				jQuery.getScript(config.relative_path + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function () {
					// Switch back to long-form
					adaptor.toggleTimeagoShorthand();
				});
			});
 
			// Add directional code if necessary
			adaptor.translate('[[language:dir]]', function (value) {
				if (value) {
					jQuery('html').css('direction', value).attr('data-dir', value);
				}
			});
		}
	};
 
	return adaptor;
}));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/uploader.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/public/src/modules/uploader.js

Statements: 9.52% (6 / 63)      Branches: 0% (0 / 34)      Functions: 0% (0 / 18)      Lines: 9.52% (6 / 63)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133        2                                                                                                   1 1                                                                                                   1           1                     1                    
'use strict';
 
/* globals define, templates */
 
define('uploader', ['translator'], function (translator) {
 
	var module = {};
 
	module.open = function (route, params, fileSize, callback) {
		console.warn('[uploader] uploader.open() is deprecated, please use uploader.show() instead, and pass parameters as a singe option with callback, e.g. uploader.show({}, callback);');
		module.show({
			route: route,
			params: params,
			fileSize: fileSize
		}, callback);
	};
 
	module.show = function (data, callback) {
		var fileSize = data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false;
		parseModal({
			showHelp: data.hasOwnProperty('showHelp') && data.showHelp !== undefined ? data.showHelp : true,
			fileSize: fileSize,
			title: data.title || '[[global:upload_file]]',
			description: data.description || '',
			button: data.button || '[[global:upload]]',
			accept: data.accept ? data.accept.replace(/,/g, '&#44; ') : ''
		}, function (uploadModal) {
			uploadModal = $(uploadModal);
 
			uploadModal.modal('show');
		 	uploadModal.on('hidden.bs.modal', function () {
				uploadModal.remove();
			});
 
			var uploadForm = uploadModal.find('#uploadForm');
			uploadForm.attr('action', data.route);
			uploadForm.find('#params').val(JSON.stringify(data.params));
 
			uploadModal.find('#fileUploadSubmitBtn').on('click', function () {
				$(this).addClass('disabled');
				uploadForm.submit();
			});
 
			uploadForm.submit(function () {
				onSubmit(uploadModal, fileSize, callback);
				return false;
			});
		});
	};
 
	module.hideAlerts = function (modal) {
		$(modal).find('#alert-status, #alert-success, #alert-error, #upload-progress-box').addClass('hide');
	};
 
	function onSubmit(uploadModal, fileSize, callback) {
		function showAlert(type, message) {
			module.hideAlerts(uploadModal);
			if (type === 'error') {
				uploadModal.find('#fileUploadSubmitBtn').removeClass('disabled');
			}
			uploadModal.find('#alert-' + type).translateText(message).removeClass('hide');
		}
 
		showAlert('status', '[[uploads:uploading-file]]');
 
		uploadModal.find('#upload-progress-bar').css('width', '0%');
		uploadModal.find('#upload-progress-box').show().removeClass('hide');
 
		var fileInput = uploadModal.find('#fileInput');
		if (!fileInput.val()) {
			return showAlert('error', '[[uploads:select-file-to-upload]]');
		}
		if (!hasValidFileSize(fileInput[0], fileSize)) {
			return showAlert('error', '[[error:file-too-big, ' + fileSize + ']]');
		}
 
		uploadModal.find('#uploadForm').ajaxSubmit({
			headers: {
				'x-csrf-token': config.csrf_token
			},
			error: function (xhr) {
				xhr = maybeParse(xhr);
				showAlert('error', xhr.responseJSON ? (xhr.responseJSON.error || xhr.statusText) : 'error uploading, code : ' + xhr.status);
			},
			uploadProgress: function (event, position, total, percent) {
				uploadModal.find('#upload-progress-bar').css('width', percent + '%');
			},
			success: function (response) {
				response = maybeParse(response);
 
				if (response.error) {
					return showAlert('error', response.error);
				}
 
				callback(response[0].url);
 
				showAlert('success', '[[uploads:upload-success]]');
				setTimeout(function () {
					module.hideAlerts(uploadModal);
					uploadModal.modal('hide');
				}, 750);
			}
		});
	}
 
	function parseModal(tplVals, callback) {
		templates.parse('partials/modals/upload_file_modal', tplVals, function (html) {
			translator.translate(html, callback);
		});
	}
 
	function maybeParse(response) {
		if (typeof response === 'string') {
			try {
				return $.parseJSON(response);
			} catch (e) {
				return {error: '[[error:parse-error]]'};
			}
		}
		return response;
	}
 
	function hasValidFileSize(fileElement, maxSize) {
		if (window.FileReader && maxSize) {
			return fileElement.files[0].size <= maxSize * 1000;
		}
		return true;
	}
 
	return module;
});
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/

Statements: 5.9% (229 / 3879)      Branches: 0.62% (12 / 1932)      Functions: 0.37% (4 / 1076)      Lines: 5.91% (229 / 3876)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/
File Statements Branches Functions Lines
analytics.js 3.64% (4 / 110) 0% (0 / 46) 0% (0 / 23) 3.64% (4 / 110)
batch.js 3.33% (2 / 60) 0% (0 / 49) 0% (0 / 13) 3.33% (2 / 60)
categories.js 2.79% (5 / 179) 0% (0 / 83) 0% (0 / 63) 2.81% (5 / 178)
coverPhoto.js 18.75% (3 / 16) 0% (0 / 4) 0% (0 / 3) 18.75% (3 / 16)
database.js 87.5% (7 / 8) 50% (1 / 2) 100% (0 / 0) 87.5% (7 / 8)
emailer.js 18.31% (13 / 71) 0% (0 / 25) 0% (0 / 21) 18.31% (13 / 71)
emitter.js 22.22% (4 / 18) 0% (0 / 6) 0% (0 / 6) 22.22% (4 / 18)
events.js 6.06% (4 / 66) 0% (0 / 20) 0% (0 / 33) 6.06% (4 / 66)
file.js 33.93% (19 / 56) 0% (0 / 14) 7.69% (1 / 13) 33.93% (19 / 56)
groups.js 2.11% (5 / 237) 0% (0 / 119) 0% (0 / 81) 2.11% (5 / 237)
hotswap.js 29.41% (5 / 17) 0% (0 / 6) 0% (0 / 3) 29.41% (5 / 17)
image.js 7.84% (4 / 51) 0% (0 / 40) 0% (0 / 15) 7.84% (4 / 51)
install.js 8.94% (21 / 235) 6.8% (10 / 147) 0% (0 / 50) 8.97% (21 / 234)
languages.js 9.8% (5 / 51) 0% (0 / 28) 0% (0 / 10) 9.8% (5 / 51)
logger.js 2.25% (2 / 89) 0% (0 / 56) 0% (0 / 17) 2.25% (2 / 89)
messaging.js 2.65% (6 / 226) 0% (0 / 128) 0% (0 / 80) 2.65% (6 / 226)
meta.js 24.39% (10 / 41) 0% (0 / 8) 12.5% (1 / 8) 25% (10 / 40)
notifications.js 3.2% (8 / 250) 0% (0 / 164) 0% (0 / 86) 3.2% (8 / 250)
pagination.js 10.26% (4 / 39) 0% (0 / 19) 0% (0 / 4) 10.26% (4 / 39)
password.js 35.29% (6 / 17) 0% (0 / 6) 20% (1 / 5) 35.29% (6 / 17)
plugins.js 3.6% (8 / 222) 0% (0 / 104) 0% (0 / 53) 3.6% (8 / 222)
posts.js 2.04% (3 / 147) 0% (0 / 84) 0% (0 / 44) 2.04% (3 / 147)
privileges.js 62.5% (5 / 8) 100% (0 / 0) 100% (0 / 0) 62.5% (5 / 8)
pubsub.js 37.5% (9 / 24) 16.67% (1 / 6) 33.33% (1 / 3) 37.5% (9 / 24)
reset.js 10.87% (10 / 92) 0% (0 / 36) 0% (0 / 17) 10.87% (10 / 92)
search.js 5.53% (12 / 217) 0% (0 / 153) 0% (0 / 63) 5.53% (12 / 217)
settings.js 4.46% (5 / 112) 0% (0 / 73) 0% (0 / 16) 4.46% (5 / 112)
sitemap.js 5.88% (4 / 68) 0% (0 / 32) 0% (0 / 15) 5.88% (4 / 68)
social.js 2.94% (1 / 34) 0% (0 / 10) 0% (0 / 12) 2.94% (1 / 34)
topics.js 2.99% (6 / 201) 0% (0 / 75) 0% (0 / 68) 2.99% (6 / 201)
upgrade.js 0.7% (4 / 571) 0% (0 / 242) 0% (0 / 157) 0.7% (4 / 571)
user.js 2.08% (4 / 192) 0% (0 / 73) 0% (0 / 71) 2.08% (4 / 192)
webserver.js 13.64% (21 / 154) 0% (0 / 74) 0% (0 / 23) 13.64% (21 / 154)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/analytics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/analytics.js

Statements: 3.64% (4 / 110)      Branches: 0% (0 / 46)      Functions: 0% (0 / 23)      Lines: 3.64% (4 / 110)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201    2 2 2   2                                                                                                                                                                                                                                                                                                                                                                                                    
'use strict';
 
var cronJob = require('cron').CronJob;
var async = require('async');
var winston = require('winston');
 
var db = require('./database');
 
var Analytics = module.exports;
 
var counters = {};
 
var pageViews = 0;
var uniqueIPCount = 0;
var uniquevisitors = 0;
 
var isCategory = /^(?:\/api)?\/category\/(\d+)/;
 
new cronJob('*/10 * * * *', function () {
	Analytics.writeData();
}, null, true);
 
Analytics.increment = function (keys) {
	keys = Array.isArray(keys) ? keys : [keys];
 
	keys.forEach(function (key) {
		counters[key] = counters[key] || 0;
		++counters[key];
	});
};
 
Analytics.pageView = function (payload) {
	++pageViews;
 
	if (payload.ip) {
		db.sortedSetScore('ip:recent', payload.ip, function (err, score) {
			if (err) {
				return;
			}
			if (!score) {
				++uniqueIPCount;
			}
			var today = new Date();
			today.setHours(today.getHours(), 0, 0, 0);
			if (!score || score < today.getTime()) {
				++uniquevisitors;
				db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
			}
		});
	}
 
	if (payload.path) {
		var categoryMatch = payload.path.match(isCategory),
			cid = categoryMatch ? parseInt(categoryMatch[1], 10) : null;
 
		if (cid) {
			Analytics.increment(['pageviews:byCid:' + cid]);
		}
	}
};
 
Analytics.writeData = function (callback) {
	callback = callback || function () {};
	var today = new Date();
	var month = new Date();
	var dbQueue = [];
 
	today.setHours(today.getHours(), 0, 0, 0);
	month.setMonth(month.getMonth(), 1);
	month.setHours(0, 0, 0, 0);
 
	if (pageViews > 0) {
		dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews', pageViews, today.getTime()));
		dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews:month', pageViews, month.getTime()));
		pageViews = 0;
	}
 
	if (uniquevisitors > 0) {
		dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:uniquevisitors', uniquevisitors, today.getTime()));
		uniquevisitors = 0;
	}
 
	if (uniqueIPCount > 0) {
		dbQueue.push(async.apply(db.incrObjectFieldBy, 'global', 'uniqueIPCount', uniqueIPCount));
		uniqueIPCount = 0;
	}
 
	if (Object.keys(counters).length > 0) {
		for(var key in counters) {
			if (counters.hasOwnProperty(key)) {
				dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:' + key, counters[key], today.getTime()));
				delete counters[key];
			}
		}
	}
 
	async.parallel(dbQueue, function (err) {
		if (err) {
			winston.error('[analytics] Encountered error while writing analytics to data store: ' + err.message);
		}
		callback(err);
	});
};
 
Analytics.getHourlyStatsForSet = function (set, hour, numHours, callback) {
	var terms = {},
		hoursArr = [];
 
	hour = new Date(hour);
	hour.setHours(hour.getHours(), 0, 0, 0);
 
	for (var i = 0, ii = numHours; i < ii; i++) {
		hoursArr.push(hour.getTime());
		hour.setHours(hour.getHours() - 1, 0, 0, 0);
	}
 
	db.sortedSetScores(set, hoursArr, function (err, counts) {
		if (err) {
			return callback(err);
		}
 
		hoursArr.forEach(function (term, index) {
			terms[term] = parseInt(counts[index], 10) || 0;
		});
 
		var termsArr = [];
 
		hoursArr.reverse();
		hoursArr.forEach(function (hour) {
			termsArr.push(terms[hour]);
		});
 
		callback(null, termsArr);
	});
};
 
Analytics.getDailyStatsForSet = function (set, day, numDays, callback) {
	var daysArr = [];
 
	day = new Date(day);
	day.setDate(day.getDate() + 1);	// set the date to tomorrow, because getHourlyStatsForSet steps *backwards* 24 hours to sum up the values
	day.setHours(0, 0, 0, 0);
 
	async.whilst(function () {
		return numDays--;
	}, function (next) {
		Analytics.getHourlyStatsForSet(set, day.getTime() - (1000 * 60 * 60 * 24 * numDays), 24, function (err, day) {
			if (err) {
				return next(err);
			}
 
			daysArr.push(day.reduce(function (cur, next) {
				return cur + next;
			}));
			next();
		});
	}, function (err) {
		callback(err, daysArr);
	});
};
 
Analytics.getUnwrittenPageviews = function () {
	return pageViews;
};
 
Analytics.getMonthlyPageViews = function (callback) {
	var thisMonth = new Date();
	var lastMonth = new Date();
	thisMonth.setMonth(thisMonth.getMonth(), 1);
	thisMonth.setHours(0, 0, 0, 0);
	lastMonth.setMonth(thisMonth.getMonth() - 1, 1);
	lastMonth.setHours(0, 0, 0, 0);
 
	var values = [thisMonth.getTime(), lastMonth.getTime()];
 
	db.sortedSetScores('analytics:pageviews:month', values, function (err, scores) {
		if (err) {
			return callback(err);
		}
		callback(null, {thisMonth: scores[0] || 0, lastMonth: scores[1] || 0});
	});
};
 
Analytics.getCategoryAnalytics = function (cid, callback) {
	async.parallel({
		'pageviews:hourly': async.apply(Analytics.getHourlyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 24),
		'pageviews:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 30),
		'topics:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:topics:byCid:' + cid, Date.now(), 7),
		'posts:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:posts:byCid:' + cid, Date.now(), 7),
	}, callback);
};
 
Analytics.getErrorAnalytics = function (callback) {
	async.parallel({
		'not-found': async.apply(Analytics.getDailyStatsForSet, 'analytics:errors:404', Date.now(), 7),
		'toobusy': async.apply(Analytics.getDailyStatsForSet, 'analytics:errors:503', Date.now(), 7)
	}, callback);
};
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/batch.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/batch.js

Statements: 3.33% (2 / 60)      Branches: 0% (0 / 49)      Functions: 0% (0 / 13)      Lines: 3.33% (2 / 60)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112        2 2                                                                                                                                                                                                                    
 
 
'use strict';
 
var async = require('async');
var db = require('./database');
var utils = require('../public/src/utils');
 
var DEFAULT_BATCH_SIZE = 100;
 
exports.processSortedSet = function (setKey, process, options, callback) {
	if (typeof options === 'function') {
		callback = options;
		options = {};
	}
 
	callback = typeof callback === 'function' ? callback : function () {};
	options = options || {};
 
	if (typeof process !== 'function') {
		return callback(new Error('[[error:process-not-a-function]]'));
	}
 
	// use the fast path if possible
	if (db.processSortedSet && typeof options.doneIf !== 'function' && !utils.isNumber(options.alwaysStartAt)) {
		return db.processSortedSet(setKey, process, options.batch || DEFAULT_BATCH_SIZE, callback);
	}
 
	// custom done condition
	options.doneIf = typeof options.doneIf === 'function' ? options.doneIf : function () {};
 
	var batch = options.batch || DEFAULT_BATCH_SIZE;
	var start = 0;
	var stop = batch;
	var done = false;
 
	async.whilst(
		function () {
			return !done;
		},
		function (next) {
			db.getSortedSetRange(setKey, start, stop, function (err, ids) {
				if (err) {
					return next(err);
				}
				if (!ids.length || options.doneIf(start, stop, ids)) {
					done = true;
					return next();
				}
				process(ids, function (err) {
					if (err) {
						return next(err);
					}
					start += utils.isNumber(options.alwaysStartAt) ? options.alwaysStartAt : batch + 1;
					stop = start + batch;
					next();
				});
			});
		},
		callback
	);
};
 
exports.processArray = function (array, process, options, callback) {
	if (typeof options === 'function') {
		callback = options;
		options = {};
	}
 
	callback = typeof callback === 'function' ? callback : function () {};
	options = options || {};
 
	if (!Array.isArray(array) || !array.length) {
		return callback();
	}
	if (typeof process !== 'function') {
		return callback(new Error('[[error:process-not-a-function]]'));
	}
 
	var batch = options.batch || DEFAULT_BATCH_SIZE;
	var start = 0;
	var done = false;
 
	async.whilst(
		function () {
			return !done;
		},
		function (next) {
			var currentBatch = array.slice(start, start + batch);
			if (!currentBatch.length) {
				done = true;
				return next();
			}
			process(currentBatch, function (err) {
				if (err) {
					return next(err);
				}
				start = start + batch;
				if (options.interval) {
					setTimeout(next, options.interval);
				} else {
					next();
				}
			});
		},
		function (err) {
			callback(err);
		}
	);
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories.js

Statements: 2.79% (5 / 179)      Branches: 0% (0 / 83)      Functions: 0% (0 / 63)      Lines: 2.81% (5 / 178)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358      18   18                                                                                                                                                                                                                                                                                                                                   1                                                                                                                                       1                                                                                                                                         1                                                                                                          
 
'use strict';
 
var async = require('async');
 
var db = require('./database');
var user = require('./user');
var Groups = require('./groups');
var plugins = require('./plugins');
var privileges = require('./privileges');
 
(function (Categories) {
 
	require('./categories/data')(Categories);
	require('./categories/create')(Categories);
	require('./categories/delete')(Categories);
	require('./categories/topics')(Categories);
	require('./categories/unread')(Categories);
	require('./categories/activeusers')(Categories);
	require('./categories/recentreplies')(Categories);
	require('./categories/update')(Categories);
 
	Categories.exists = function (cid, callback) {
		db.isSortedSetMember('categories:cid', cid, callback);
	};
 
	Categories.getCategoryById = function (data, callback) {
		var category;
		async.waterfall([
			function (next) {
				Categories.getCategories([data.cid], data.uid, next);
			},
			function (categories, next) {
				if (!Array.isArray(categories) || !categories[0]) {
					return next(new Error('[[error:invalid-cid]]'));
				}
				category = categories[0];
 
				async.parallel({
					topics: function (next) {
						Categories.getCategoryTopics(data, next);
					},
					topicCount: function (next) {
						if (Array.isArray(data.set)) {
							db.sortedSetIntersectCard(data.set, next);
						} else {
							next(null, category.topic_count);
						}
					},
					isIgnored: function (next) {
						Categories.isIgnored([data.cid], data.uid, next);
					}
				}, next);
			},
			function (results, next) {
				category.topics = results.topics.topics;
				category.nextStart = results.topics.nextStart;
				category.isIgnored = results.isIgnored[0];
				category.topic_count = results.topicCount;
 
				plugins.fireHook('filter:category.get', {category: category, uid: data.uid}, next);
			},
			function (data, next) {
				next(null, data.category);
			}
		], callback);
	};
 
	Categories.isIgnored = function (cids, uid, callback) {
		db.isSortedSetMembers('uid:' + uid + ':ignored:cids', cids, callback);
	};
 
	Categories.getPageCount = function (cid, uid, callback) {
		async.parallel({
			topicCount: async.apply(Categories.getCategoryField, cid, 'topic_count'),
			settings: async.apply(user.getSettings, uid)
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (!parseInt(results.topicCount, 10)) {
				return callback(null, 1);
			}
 
			callback(null, Math.ceil(parseInt(results.topicCount, 10) / results.settings.topicsPerPage));
		});
	};
 
	Categories.getAllCategories = function (uid, callback) {
		db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
			if (err || !Array.isArray(cids) || !cids.length) {
				return callback(err, []);
			}
 
			Categories.getCategories(cids, uid, callback);
		});
	};
 
	Categories.getCategoriesByPrivilege = function (set, uid, privilege, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange(set, 0, -1, next);
			},
			function (cids, next) {
				privileges.categories.filterCids(privilege, cids, uid, next);
			},
			function (cids, next) {
				Categories.getCategories(cids, uid, next);
			}
		], callback);
	};
 
	Categories.getModerators = function (cid, callback) {
		Groups.getMembers('cid:' + cid + ':privileges:mods', 0, -1, function (err, uids) {
			if (err || !Array.isArray(uids) || !uids.length) {
				return callback(err, []);
			}
 
			user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture'], callback);
		});
	};
 
 
	Categories.getCategories = function (cids, uid, callback) {
		if (!Array.isArray(cids)) {
			return callback(new Error('[[error:invalid-cid]]'));
		}
 
		if (!cids.length) {
			return callback(null, []);
		}
 
		async.parallel({
			categories: function (next) {
				Categories.getCategoriesData(cids, next);
			},
			children: function (next) {
				Categories.getChildren(cids, uid, next);
			},
			parents: function (next) {
				Categories.getParents(cids, next);
			},
			hasRead: function (next) {
				Categories.hasReadCategories(cids, uid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var categories = results.categories;
			var hasRead = results.hasRead;
			uid = parseInt(uid, 10);
			for(var i = 0; i < results.categories.length; ++i) {
				if (categories[i]) {
					categories[i]['unread-class'] = (parseInt(categories[i].topic_count, 10) === 0 || (hasRead[i] && uid !== 0)) ? '' : 'unread';
					categories[i].children = results.children[i];
					categories[i].parent = results.parents[i] || undefined;
					calculateTopicPostCount(categories[i]);
				}
			}
 
			callback(null, categories);
		});
	};
 
	function calculateTopicPostCount(category) {
		if (!category) {
			return;
		}
 
		var postCount = parseInt(category.post_count, 10) || 0;
		var topicCount = parseInt(category.topic_count, 10) || 0;
		if (!Array.isArray(category.children) || !category.children.length) {
			category.totalPostCount = postCount;
			category.totalTopicCount = topicCount;
			return;
		}
 
		category.children.forEach(function (child) {
			calculateTopicPostCount(child);
			postCount += parseInt(child.totalPostCount, 10) || 0;
			topicCount += parseInt(child.totalTopicCount, 10) || 0;
		});
 
		category.totalPostCount = postCount;
		category.totalTopicCount = topicCount;
	}
 
	Categories.getParents = function (cids, callback) {
		var categoriesData;
		var parentCids;
		async.waterfall([
			function (next) {
				Categories.getCategoriesFields(cids, ['parentCid'], next);
			},
			function (_categoriesData, next) {
				categoriesData = _categoriesData;
 
				parentCids = categoriesData.filter(function (category) {
					return category && category.hasOwnProperty('parentCid') && parseInt(category.parentCid, 10);
				}).map(function (category) {
					return parseInt(category.parentCid, 10);
				});
 
				if (!parentCids.length) {
					return callback(null, cids.map(function () {return null;}));
				}
 
				Categories.getCategoriesData(parentCids, next);
			},
			function (parentData, next) {
				parentData = categoriesData.map(function (category) {
					return parentData[parentCids.indexOf(parseInt(category.parentCid, 10))];
				});
				next(null, parentData);
			}
		], callback);
	};
 
	Categories.getChildren = function (cids, uid, callback) {
		var categories = cids.map(function (cid) {
			return {cid: cid};
		});
 
		async.each(categories, function (category, next) {
			getChildrenRecursive(category, uid, next);
		}, function (err) {
			callback(err, categories.map(function (c) {
				return c && c.children;
			}));
		});
	};
 
	function getChildrenRecursive(category, uid, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('cid:' + category.cid + ':children', 0, -1, next);
			},
			function (children, next) {
				privileges.categories.filterCids('find', children, uid, next);
			},
			function (children, next) {
				children = children.filter(function (cid) {
					return parseInt(category.cid, 10) !== parseInt(cid, 10);
				});
				if (!children.length) {
					category.children = [];
					return callback();
				}
				Categories.getCategoriesData(children, next);
			},
			function (childrenData, next) {
				childrenData = childrenData.filter(Boolean);
				category.children = childrenData;
				async.each(category.children, function (child, next) {
					getChildrenRecursive(child, uid, next);
				}, next);
			}
		], callback);
	}
 
	Categories.flattenCategories = function (allCategories, categoryData) {
		categoryData.forEach(function (category) {
			if (!category) {
				return;
			}
 
			if (!category.parent) {
				allCategories.push(category);
			}
 
			if (Array.isArray(category.children) && category.children.length) {
				Categories.flattenCategories(allCategories, category.children);
			}
		});
	};
 
	/**
	 * Recursively build tree
	 *
	 * @param categories {array} flat list of categories
	 * @param parentCid {number} start from 0 to build full tree
	 */
	Categories.getTree = function (categories, parentCid) {
		var tree = [], i = 0, len = categories.length, category;
 
		for (i; i < len; ++i) {
			category = categories[i];
			if (!category.hasOwnProperty('parentCid') || category.parentCid === null) {
				category.parentCid = 0;
			}
 
			if (parseInt(category.parentCid, 10) === parseInt(parentCid, 10)) {
				tree.push(category);
				category.children = Categories.getTree(categories, category.cid);
			}
		}
 
		return tree;
	};
 
	Categories.buildForSelect = function (uid, callback) {
		function recursive(category, categoriesData, level) {
			if (category.link) {
				return;
			}
 
			var bullet = level ? '&bull; ' : '';
			category.value = category.cid;
			category.text = level + bullet + category.name;
			categoriesData.push(category);
 
			category.children.forEach(function (child) {
				recursive(child, categoriesData, '&nbsp;&nbsp;&nbsp;&nbsp;' + level);
			});
		}
		Categories.getCategoriesByPrivilege('cid:0:children', uid, 'read', function (err, categories) {
			if (err) {
				return callback(err);
			}
 
			var categoriesData = [];
 
			categories = categories.filter(function (category) {
				return category && !category.link && !parseInt(category.parentCid, 10);
			});
 
			categories.forEach(function (category) {
				recursive(category, categoriesData, '');
			});
			callback(null, categoriesData);
		});
	};
 
	Categories.getIgnorers = function (cid, start, stop, callback) {
		db.getSortedSetRevRange('cid:' + cid + ':ignorers', start, stop, callback);
	};
 
	Categories.filterIgnoringUids = function (cid, uids, callback) {
		async.waterfall([
			function (next) {
				db.sortedSetScores('cid:' + cid + ':ignorers', uids, next);
			},
			function (scores, next) {
				var readingUids = uids.filter(function (uid, index) {
					return uid && !!scores[index];
				});
				next(null, readingUids);
			}
		], callback);
	};
 
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/coverPhoto.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/coverPhoto.js

Statements: 18.75% (3 / 16)      Branches: 0% (0 / 4)      Functions: 0% (0 / 3)      Lines: 18.75% (3 / 16)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33    2 2                       1                                  
"use strict";
 
var coverPhoto = {};
var meta = require('./meta');
var nconf = require('nconf');
 
 
coverPhoto.getDefaultGroupCover = function (groupName) {
	return getCover('groups', groupName);
};
 
coverPhoto.getDefaultProfileCover = function (uid) {
	return getCover('profile', parseInt(uid, 10));
};
 
function getCover(type, id) {
	if (meta.config[type + ':defaultCovers']) {		
		var covers = meta.config[type + ':defaultCovers'].split(/\s*?,\s*?/g);
		
		if (typeof id === 'string') {
			id = (id.charCodeAt(0) + id.charCodeAt(1)) % covers.length;
		} else {
			id = id % covers.length;
		}
 
		return covers[id];
	}
 
	return nconf.get('relative_path') + '/images/cover-default.png';
}
 
module.exports = coverPhoto;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database.js

Statements: 87.5% (7 / 8)      Branches: 50% (1 / 2)      Functions: 100% (0 / 0)      Lines: 87.5% (7 / 8)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15    422 422 422   422 422 422     422      
"use strict";
 
var nconf = require('nconf');
var databaseName = nconf.get('database');
var winston = require('winston');
 
Eif (!databaseName) {
	winston.error(new Error('Database type not set! Run ./nodebb setup'));
	process.exit();
}
 
var primaryDB = require('./database/' + databaseName);
 
module.exports = primaryDB;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/emailer.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/emailer.js

Statements: 18.31% (13 / 71)      Branches: 0% (0 / 25)      Functions: 0% (0 / 21)      Lines: 18.31% (13 / 71)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165    2 2 2 2 2 2 2 2 2   2                                                                                                                                                                                                                                                         1                 1               1                    
"use strict";
 
var async = require('async');
var winston = require('winston');
var nconf = require('nconf');
var templates = require('templates.js');
var nodemailer = require('nodemailer');
var sendmailTransport = require('nodemailer-sendmail-transport');
var smtpTransport = require('nodemailer-smtp-transport');
var htmlToText = require('html-to-text');
var url = require('url');
 
var User = require('./user');
var Plugins = require('./plugins');
var meta = require('./meta');
var translator = require('../public/src/modules/translator');
 
var transports = {
	sendmail: nodemailer.createTransport(sendmailTransport()),
	gmail: undefined
};
 
var app;
var fallbackTransport;
 
(function (Emailer) {
	Emailer.registerApp = function (expressApp) {
		app = expressApp;
 
		// Enable Gmail transport if enabled in ACP
		if (parseInt(meta.config['email:GmailTransport:enabled'], 10) === 1) {
			fallbackTransport = transports.gmail = nodemailer.createTransport(smtpTransport({
				host: 'smtp.gmail.com',
				port: 465,
				secure: true,
				auth: {
					user: meta.config['email:GmailTransport:user'],
					pass: meta.config['email:GmailTransport:pass']
				}
			}));
		} else {
			fallbackTransport = transports.sendmail;
		}
 
		return Emailer;
	};
 
	Emailer.send = function (template, uid, params, callback) {
		callback = callback || function () {};
		if (!app) {
			winston.warn('[emailer] App not ready!');
			return callback();
		}
 
		async.waterfall([
			function (next) {
				async.parallel({
					email: async.apply(User.getUserField, uid, 'email'),
					settings: async.apply(User.getSettings, uid)
				}, next);
			},
			function (results, next) {
				if (!results.email) {
					winston.warn('uid : ' + uid + ' has no email, not sending.');
					return next();
				}
				params.uid = uid;
				Emailer.sendToEmail(template, results.email, results.settings.userLang, params, next);
			}
		], callback);
	};
 
	Emailer.sendToEmail = function (template, email, language, params, callback) {
		callback = callback || function () {};
 
		var lang = language || meta.config.defaultLang || 'en-GB';
 
		async.waterfall([
			function (next) {
				async.parallel({
					html: function (next) {
						renderAndTranslate('emails/' + template, params, lang, next);
					},
					subject: function (next) {
						translator.translate(params.subject, lang, function (translated) {
							next(null, translated);
						});
					}
				}, next);
			},
			function (results, next) {
				var data = {
					_raw: params,
					to: email,
					from: meta.config['email:from'] || 'no-reply@' + getHostname(),
					from_name: meta.config['email:from_name'] || 'NodeBB',
					subject: results.subject,
					html: results.html,
					plaintext: htmlToText.fromString(results.html, {
						ignoreImage: true
					}),
					template: template,
					uid: params.uid,
					pid: params.pid,
					fromUid: params.fromUid
				};
				Plugins.fireHook('filter:email.modify', data, next);
			},
			function (data, next) {
				if (Plugins.hasListeners('filter:email.send')) {
					Plugins.fireHook('filter:email.send', data, next);
				} else {
					Emailer.sendViaFallback(data, next);
				}
			}
		], function (err) {
			if (err && err.code === 'ENOENT') {
				callback(new Error('[[error:sendmail-not-found]]'));
			} else {
				callback(err);
			}
		});
	};
 
	Emailer.sendViaFallback = function (data, callback) {
		// Some minor alterations to the data to conform to nodemailer standard
		data.text = data.plaintext;
		delete data.plaintext;
 
		// NodeMailer uses a combined "from"
		data.from = data.from_name + '<' + data.from + '>';
		delete data.from_name;
 
		winston.verbose('[emailer] Sending email to uid ' + data.uid);
		fallbackTransport.sendMail(data, callback);
	};
 
	function render(tpl, params, next) {
		if (meta.config['email:custom:' + tpl.replace('emails/', '')]) {
			var text = templates.parse(meta.config['email:custom:' + tpl.replace('emails/', '')], params);
			next(null, text);
		} else {
			app.render(tpl, params, next);
		}
	}
 
	function renderAndTranslate(tpl, params, lang, callback) {
		render(tpl, params, function (err, html) {
			translator.translate(html, lang, function (translated) {
				callback(err, translated);
			});
		});
	}
 
	function getHostname() {
		var configUrl = nconf.get('url');
		var parsed = url.parse(configUrl);
 
		return parsed.hostname;
	}
 
}(module.exports));
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/emitter.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/emitter.js

Statements: 22.22% (4 / 18)      Branches: 0% (0 / 6)      Functions: 0% (0 / 6)      Lines: 22.22% (4 / 18)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36    1     1                                 1                       1  
"use strict";
 
var eventEmitter = new (require('events')).EventEmitter();
 
 
eventEmitter.all = function (events, callback) {
	var eventList = events.slice(0);
 
	events.forEach(function onEvent(event) {
		eventEmitter.on(event, function () {
			var index = eventList.indexOf(event);
			if (index === -1) {
				return;
			}
			eventList.splice(index, 1);
			if (eventList.length === 0) {
				callback();
			}
		});
	});
};
 
eventEmitter.any = function (events, callback) {
	events.forEach(function onEvent(event) {
		eventEmitter.on(event, function () {
			if (events !== null) {
				callback();
			}
 
			events = null;
		});
	});
};
 
module.exports = eventEmitter;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/events.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/events.js

Statements: 6.06% (4 / 66)      Branches: 0% (0 / 20)      Functions: 0% (0 / 33)      Lines: 6.06% (4 / 66)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139      2 2   2                                                                                                                                   1                                                                                                                                    
 
'use strict';
 
var async = require('async');
var validator = require('validator');
 
var db =  require('./database');
var batch = require('./batch');
var user = require('./user');
var utils = require('../public/src/utils');
 
(function (events) {
	events.log = function (data, callback) {
		callback = callback || function () {};
 
		async.waterfall([
			function (next) {
				db.incrObjectField('global', 'nextEid', next);
			},
			function (eid, next) {
				data.timestamp = Date.now();
				data.eid = eid;
 
				async.parallel([
					function (next) {
						db.sortedSetAdd('events:time', data.timestamp, eid, next);
					},
					function (next) {
						db.setObject('event:' + eid, data, next);
					}
				], next);
			}
		], function (err, result) {
			callback(err);
		});
	};
 
	events.getEvents = function (start, stop, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange('events:time', start, stop, next);
			},
			function (eids, next) {
				var keys = eids.map(function (eid) {
					return 'event:' + eid;
				});
				db.getObjects(keys, next);
			},
			function (eventsData, next) {
				eventsData = eventsData.filter(Boolean);
				addUserData(eventsData, 'uid', 'user', next);
			},
			function (eventsData, next) {
				addUserData(eventsData, 'targetUid', 'targetUser', next);
			},
			function (eventsData, next) {
				eventsData.forEach(function (event) {
					Object.keys(event).forEach(function (key) {
						if (typeof event[key] === 'string') {
							event[key] = validator.escape(String(event[key] || ''));
						}
					});
					var e = utils.merge(event);
					e.eid = e.uid = e.type = e.ip = e.user = undefined;
					event.jsonString = JSON.stringify(e, null, 4);
					event.timestampISO = new Date(parseInt(event.timestamp, 10)).toUTCString();
				});
				next(null, eventsData);
			}
		], callback);
	};
 
	function addUserData(eventsData, field, objectName, callback) {
		var uids = eventsData.map(function (event) {
			return event && event[field];
		}).filter(function (uid, index, array) {
			return uid && array.indexOf(uid) === index;
		});
 
		if (!uids.length) {
			return callback(null, eventsData);
		}
 
		async.parallel({
			isAdmin: function (next) {
				user.isAdministrator(uids, next);
			},
			userData: function (next) {
				user.getUsersFields(uids, ['username', 'userslug', 'picture'], next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var userData = results.userData;
 
			var map = {};
			userData.forEach(function (user, index) {
				user.isAdmin = results.isAdmin[index];
				map[user.uid] = user;
			});
 
			eventsData.forEach(function (event) {
				if (map[event[field]]) {
					event[objectName] = map[event[field]];
				}
			});
			callback(null, eventsData);
		});
	}
 
	events.deleteEvents = function (eids, callback) {
		callback = callback || function () {};
		async.parallel([
			function (next) {
				var keys = eids.map(function (eid) {
					return 'event:' + eid;
				});
				db.deleteAll(keys, next);
			},
			function (next) {
				db.sortedSetRemove('events:time', eids, next);
			}
		], callback);
	};
 
	events.deleteAll = function (callback) {
		callback = callback || function () {};
 
		batch.processSortedSet('events:time', function (eids, next) {
			events.deleteEvents(eids, next);
		}, {alwaysStartAt: 0}, callback);
	};
 
 
}(module.exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/file.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/file.js

Statements: 33.93% (19 / 56)      Branches: 0% (0 / 14)      Functions: 7.69% (1 / 13)      Lines: 33.93% (19 / 56)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107    1 1 1 1 1   1   1   1                                                       1                     1                           1                                           1           1 1 1 1   1     1     1    
"use strict";
 
var fs = require('fs');
var nconf = require('nconf');
var path = require('path');
var winston = require('winston');
var jimp = require('jimp');
 
var utils = require('../public/src/utils');
 
var file = {};
 
file.saveFileToLocal = function (filename, folder, tempPath, callback) {
	/*
	* remarkable doesn't allow spaces in hyperlinks, once that's fixed, remove this.
	*/
	filename = filename.split('.');
	filename.forEach(function (name, idx) {
		filename[idx] = utils.slugify(name);
	});
	filename = filename.join('.');
 
	var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), folder, filename);
 
	winston.verbose('Saving file ' + filename + ' to : ' + uploadPath);
 
	var is = fs.createReadStream(tempPath);
	var os = fs.createWriteStream(uploadPath);
	is.on('end', function () {
		callback(null, {
			url: nconf.get('upload_url') + '/' + folder + '/' + filename,
			path: uploadPath
		});
	});
 
	os.on('error', callback);
 
	is.pipe(os);
};
 
file.base64ToLocal = function (imageData, uploadPath, callback) {
	var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64');
	uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), uploadPath);
 
	fs.writeFile(uploadPath, buffer, {
		encoding: 'base64'
	}, function (err) {
		callback(err, uploadPath);
	});
};
 
file.isFileTypeAllowed = function (path, callback) {
	var plugins = require('./plugins');
	if (plugins.hasListeners('filter:file.isFileTypeAllowed')) {
		return plugins.fireHook('filter:file.isFileTypeAllowed', path, function (err) {
			callback(err);
		});
	}
 
	// Attempt to read the file, if it passes, file type is allowed
	jimp.read(path, function (err) {
		callback(err);
	});
};
 
file.allowedExtensions = function () {
	var meta = require('./meta');
	var allowedExtensions = (meta.config.allowedFileExtensions || '').trim();
	if (!allowedExtensions) {
		return [];
	}
	allowedExtensions = allowedExtensions.split(',');
	allowedExtensions = allowedExtensions.filter(Boolean).map(function (extension) {
		extension = extension.trim();
		if (!extension.startsWith('.')) {
			extension = '.' + extension;
		}
		return extension;
	});
 
	if (allowedExtensions.indexOf('.jpg') !== -1 && allowedExtensions.indexOf('.jpeg') === -1) {
		allowedExtensions.push('.jpeg');
	}
 
	return allowedExtensions;
};
 
file.exists = function (path, callback) {
	fs.stat(path, function (err, stat) {
		callback(!err && stat);
	});
};
 
file.existsSync = function (path) {
	var exists = false;
	try {
		exists = fs.statSync(path);
	} catch(err) {
		exists = false;
	}
 
	return !!exists;
};
 
module.exports = file;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups.js

Statements: 2.11% (5 / 237)      Branches: 0% (0 / 119)      Functions: 0% (0 / 81)      Lines: 2.11% (5 / 237)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464    84 84   84 72                                                                                                                               1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
'use strict';
 
var async = require('async');
var validator = require('validator');
 
var user = require('./user');
var db = require('./database');
var plugins = require('./plugins');
var posts = require('./posts');
var privileges = require('./privileges');
var utils = require('../public/src/utils');
 
(function (Groups) {
 
	require('./groups/create')(Groups);
	require('./groups/delete')(Groups);
	require('./groups/update')(Groups);
	require('./groups/membership')(Groups);
	require('./groups/ownership')(Groups);
	require('./groups/search')(Groups);
	require('./groups/cover')(Groups);
 
	var ephemeralGroups = ['guests'],
 
		internals = {
			getEphemeralGroup: function (groupName) {
				return {
					name: groupName,
					slug: utils.slugify(groupName),
					description: '',
					deleted: '0',
					hidden: '0',
					system: '1'
				};
			},
			removeEphemeralGroups: function (groups) {
				var x = groups.length;
				while(x--) {
					if (ephemeralGroups.indexOf(groups[x]) !== -1) {
						groups.splice(x, 1);
					}
				}
 
				return groups;
			}
		};
 
	Groups.internals = internals;
 
	var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/;
	Groups.isPrivilegeGroup = function (groupName) {
		return isPrivilegeGroupRegex.test(groupName);
	};
 
	Groups.getEphemeralGroups = function () {
		return ephemeralGroups;
	};
 
	Groups.getGroupsFromSet = function (set, uid, start, stop, callback) {
		var method;
		var args;
		if (set === 'groups:visible:name') {
			method = db.getSortedSetRangeByLex;
			args = [set, '-', '+', start, stop - start + 1, done];
		} else {
			method = db.getSortedSetRevRange;
			args = [set, start, stop, done];
		}
		method.apply(null, args);
 
		function done(err, groupNames) {
			if (err) {
				return callback(err);
			}
 
			if (set === 'groups:visible:name') {
				groupNames = groupNames.map(function (name) {
					return name.split(':')[1];
				});
			}
 
			Groups.getGroupsAndMembers(groupNames, callback);
		}
	};
 
	Groups.getGroups = function (set, start, stop, callback) {
		db.getSortedSetRevRange(set, start, stop, callback);
	};
 
	Groups.getGroupsAndMembers = function (groupNames, callback) {
		async.parallel({
			groups: function (next) {
				Groups.getGroupsData(groupNames, next);
			},
			members: function (next) {
				Groups.getMemberUsers(groupNames, 0, 3, next);
			}
		}, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			data.groups.forEach(function (group, index) {
				if (!group) {
					return;
				}
 
				group.members = data.members[index] || [];
				group.truncated = group.memberCount > data.members.length;
			});
 
			callback(null, data.groups);
		});
	};
 
	Groups.get = function (groupName, options, callback) {
		if (!groupName) {
			return callback(new Error('[[error:invalid-group]]'));
		}
 
		var stop = -1;
 
		async.parallel({
			base: function (next) {
				db.getObject('group:' + groupName, next);
			},
			members: function (next) {
				if (options.truncateUserList) {
					stop = (parseInt(options.userListCount, 10) || 4) - 1;
				}
 
				Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next);
			},
			pending: function (next) {
				async.waterfall([
					function (next) {
						db.getSetMembers('group:' + groupName + ':pending', next);
					},
					function (uids, next) {
						user.getUsersData(uids, next);
					}
				], next);
			},
			invited: function (next) {
				async.waterfall([
					function (next) {
						db.getSetMembers('group:' + groupName + ':invited', next);
					},
					function (uids, next) {
						user.getUsersData(uids, next);
					}
				], next);
			},
			isMember: async.apply(Groups.isMember, options.uid, groupName),
			isPending: async.apply(Groups.isPending, options.uid, groupName),
			isInvited: async.apply(Groups.isInvited, options.uid, groupName),
			isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName)
		}, function (err, results) {
			if (err) {
				return callback(err);
			} else if (!results.base) {
				return callback(new Error('[[error:no-group]]'));
			}
 
			results.base['cover:url'] = results.base['cover:url'] || require('./coverPhoto').getDefaultGroupCover(groupName);
			results.base['cover:position'] = results.base['cover:position'] || '50% 50%';
 
			plugins.fireHook('filter:parse.raw', results.base.description, function (err, descriptionParsed) {
				if (err) {
					return callback(err);
				}
 
				Groups.escapeGroupData(results.base);
 
				results.base.descriptionParsed = descriptionParsed;
				results.base.userTitleEnabled = results.base.userTitleEnabled ? !!parseInt(results.base.userTitleEnabled, 10) : true;
				results.base.createtimeISO = utils.toISOString(results.base.createtime);
				results.base.members = results.members;
				results.base.membersNextStart = stop + 1;
				results.base.pending = results.pending.filter(Boolean);
				results.base.invited = results.invited.filter(Boolean);
				results.base.deleted = !!parseInt(results.base.deleted, 10);
				results.base.hidden = !!parseInt(results.base.hidden, 10);
				results.base.system = !!parseInt(results.base.system, 10);
				results.base.memberCount = parseInt(results.base.memberCount, 10);
				results.base.private = (results.base.private === null || results.base.private === undefined) ? true : !!parseInt(results.base.private, 10);
				results.base.disableJoinRequests = parseInt(results.base.disableJoinRequests, 10) === 1;
				results.base.isMember = results.isMember;
				results.base.isPending = results.isPending;
				results.base.isInvited = results.isInvited;
				results.base.isOwner = results.isOwner;
 
				plugins.fireHook('filter:group.get', {group: results.base}, function (err, data) {
					callback(err, data ? data.group : null);
				});
			});
		});
	};
 
	Groups.getOwners = function (groupName, callback) {
		db.getSetMembers('group:' + groupName + ':owners', callback);
	};
 
	Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) {
		async.parallel({
			owners: function (next) {
				async.waterfall([
					function (next) {
						db.getSetMembers('group:' + groupName + ':owners', next);
					},
					function (uids, next) {
						user.getUsers(uids, uid, next);
					}
				], next);
			},
			members: function (next) {
				user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var ownerUids = [];
			results.owners.forEach(function (user) {
				if (user) {
					user.isOwner = true;
					ownerUids.push(user.uid.toString());
				}
			});
 
			results.members = results.members.filter(function (user) {
				return user && user.uid && ownerUids.indexOf(user.uid.toString()) === -1;
			});
			results.members = results.owners.concat(results.members);
 
			callback(null, results.members);
		});
	};
 
	Groups.escapeGroupData = function (group) {
		if (group) {
			group.nameEncoded = encodeURIComponent(group.name);
			group.displayName = validator.escape(String(group.name));
			group.description = validator.escape(String(group.description || ''));
			group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName;
		}
	};
 
	Groups.getByGroupslug = function (slug, options, callback) {
		db.getObjectField('groupslug:groupname', slug, function (err, groupName) {
			if (err) {
				return callback(err);
			} else if (!groupName) {
				return callback(new Error('[[error:no-group]]'));
			}
 
			Groups.get(groupName, options, callback);
		});
	};
 
	Groups.getGroupNameByGroupSlug = function (slug, callback) {
		db.getObjectField('groupslug:groupname', slug, callback);
	};
 
	Groups.getGroupFields = function (groupName, fields, callback) {
		Groups.getMultipleGroupFields([groupName], fields, function (err, groups) {
			callback(err, groups ? groups[0] : null);
		});
	};
 
	Groups.getMultipleGroupFields = function (groups, fields, callback) {
		db.getObjectsFields(groups.map(function (group) {
			return 'group:' + group;
		}), fields, callback);
	};
 
	Groups.setGroupField = function (groupName, field, value, callback) {
		db.setObjectField('group:' + groupName, field, value, function (err) {
			if (err) {
				return callback(err);
			}
			plugins.fireHook('action:group.set', {field: field, value: value, type: 'set'});
			callback();
		});
	};
 
	Groups.isPrivate = function (groupName, callback) {
		db.getObjectField('group:' + groupName, 'private', function (err, isPrivate) {
			if (err) {
				return callback(err);
			}
 
			callback(null, (parseInt(isPrivate, 10) === 0) ? false : true);
		});
	};
 
	Groups.isHidden = function (groupName, callback) {
		db.getObjectField('group:' + groupName, 'hidden', function (err, isHidden) {
			if (err) {
				return callback(err);
			}
 
			callback(null, parseInt(isHidden, 10) === 1);
		});
	};
 
	Groups.exists = function (name, callback) {
		if (Array.isArray(name)) {
			var slugs = name.map(function (groupName) {
					return utils.slugify(groupName);
				});
			async.parallel([
				function (next) {
					next(null, slugs.map(function (slug) {
						return ephemeralGroups.indexOf(slug) !== -1;
					}));
				},
				async.apply(db.isSortedSetMembers, 'groups:createtime', name)
			], function (err, results) {
				if (err) {
					return callback(err);
				}
				callback(null, name.map(function (n, index) {
					return results[0][index] || results[1][index];
				}));
			});
		} else {
			var slug = utils.slugify(name);
			async.parallel([
				function (next) {
					next(null, ephemeralGroups.indexOf(slug) !== -1);
				},
				async.apply(db.isSortedSetMember, 'groups:createtime', name)
			], function (err, results) {
				callback(err, !err ? (results[0] || results[1]) : null);
			});
		}
	};
 
	Groups.existsBySlug = function (slug, callback) {
		if (Array.isArray(slug)) {
			db.isObjectFields('groupslug:groupname', slug, callback);
		} else {
			db.isObjectField('groupslug:groupname', slug, callback);
		}
	};
 
	Groups.getLatestMemberPosts = function (groupName, max, uid, callback) {
		async.waterfall([
			function (next) {
				Groups.getMembers(groupName, 0, -1, next);
			},
			function (uids, next) {
				if (!Array.isArray(uids) || !uids.length) {
					return callback(null, []);
				}
				var keys = uids.map(function (uid) {
					return 'uid:' + uid + ':posts';
				});
				db.getSortedSetRevRange(keys, 0, max - 1, next);
			},
			function (pids, next) {
				privileges.posts.filter('read', pids, uid, next);
			},
			function (pids, next) {
				posts.getPostSummaryByPids(pids, uid, {stripTags: false}, next);
			}
		], callback);
	};
 
	Groups.getGroupData = function (groupName, callback) {
		Groups.getGroupsData([groupName], function (err, groupsData) {
			callback(err, Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null);
		});
	};
 
	Groups.getGroupsData = function (groupNames, callback) {
		if (!Array.isArray(groupNames) || !groupNames.length) {
			return callback(null, []);
		}
 
		var keys = groupNames.map(function (groupName) {
			return 'group:' + groupName;
		});
 
		var ephemeralIdx = groupNames.reduce(function (memo, cur, idx) {
			if (ephemeralGroups.indexOf(cur) !== -1) {
				memo.push(idx);
			}
			return memo;
		}, []);
 
		db.getObjects(keys, function (err, groupData) {
			if (err) {
				return callback(err);
			}
 
			if (ephemeralIdx.length) {
				ephemeralIdx.forEach(function (idx) {
					groupData[idx] = internals.getEphemeralGroup(groupNames[idx]);
				});
			}
 
			groupData.forEach(function (group) {
				if (group) {
					Groups.escapeGroupData(group);
					group.userTitleEnabled = group.userTitleEnabled ? parseInt(group.userTitleEnabled, 10) === 1 : true;
					group.labelColor = group.labelColor || '#000000';
					group.createtimeISO = utils.toISOString(group.createtime);
					group.hidden = parseInt(group.hidden, 10) === 1;
					group.system = parseInt(group.system, 10) === 1;
					group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10);
					group.disableJoinRequests = parseInt(group.disableJoinRequests) === 1;
 
					group['cover:url'] = group['cover:url'] || require('./coverPhoto').getDefaultGroupCover(group.name);
					group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url'];
					group['cover:position'] = group['cover:position'] || '50% 50%';
				}
			});
 
			plugins.fireHook('filter:groups.get', {groups: groupData}, function (err, data) {
				callback(err, data ? data.groups : null);
			});
		});
	};
 
	Groups.getUserGroups = function (uids, callback) {
		Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback);
	};
 
	Groups.getUserGroupsFromSet = function (set, uids, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange(set, 0, -1, next);
			},
			function (groupNames, next) {
				var groupSets = groupNames.map(function (name) {
					return 'group:' + name + ':members';
				});
 
				async.map(uids, function (uid, next) {
					db.isMemberOfSortedSets(groupSets, uid, function (err, isMembers) {
						if (err) {
							return next(err);
						}
 
						var memberOf = [];
						isMembers.forEach(function (isMember, index) {
							if (isMember) {
								memberOf.push(groupNames[index]);
							}
						});
 
						Groups.getGroupsData(memberOf, next);
					});
				}, next);
			}
		], callback);
	};
 
}(module.exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/hotswap.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/hotswap.js

Statements: 29.41% (5 / 17)      Branches: 0% (0 / 6)      Functions: 0% (0 / 3)      Lines: 29.41% (5 / 17)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35    1       1       1                       1                     1  
"use strict";
 
var HotSwap = {},
	winston = require('winston'),
	stack;
 
HotSwap.prepare = function (app) {
	stack = app._router.stack;
};
 
HotSwap.find = function (id) {
	if (stack) {
		for(var x = 0,numEntries = stack.length; x < numEntries; x++) {
			if (stack[x].handle.hotswapId === id) {
				return x;
			}
		}
	} else {
		winston.error('[hotswap] HotSwap module has not been prepared!');
	}
};
 
HotSwap.replace = function (id, router) {
	var idx = HotSwap.find(id);
	if (idx) {
		delete stack[idx].handle;	// Destroy the old router
		stack[idx].handle = router;	// Replace with the new one
		winston.verbose('[hotswap] Router with id `' + id + '` replaced successfully');
	} else {
		winston.warn('[hotswap] Could not find router in stack with hotswapId `' + id + '`');
	}
};
 
module.exports = HotSwap;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/image.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/image.js

Statements: 7.84% (4 / 51)      Branches: 0% (0 / 40)      Functions: 0% (0 / 15)      Lines: 7.84% (4 / 51)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117    4 4 4 4                                                                                                                                                                                                                              
'use strict';
 
var fs = require('fs');
var Jimp = require('jimp');
var async = require('async');
var plugins = require('./plugins');
 
var image = module.exports;
 
image.resizeImage = function (data, callback) {
	if (plugins.hasListeners('filter:image.resize')) {
		plugins.fireHook('filter:image.resize', {
			path: data.path,
			target: data.target,
			extension: data.extension,
			width: data.width,
			height: data.height
		}, function (err) {
			callback(err);
		});
	} else {
		new Jimp(data.path, function (err, image) {
			if (err) {
				return callback(err);
			}
 
			var w = image.bitmap.width,
				h = image.bitmap.height,
				origRatio = w / h,
				desiredRatio = data.width && data.height ? data.width / data.height : origRatio,
				x = 0,
				y = 0,
				crop;
 
			if (origRatio !== desiredRatio) {
				if (desiredRatio > origRatio) {
					desiredRatio = 1 / desiredRatio;
				}
				if (origRatio >= 1) {
					y = 0;	// height is the smaller dimension here
					x = Math.floor((w / 2) - (h * desiredRatio / 2));
					crop = async.apply(image.crop.bind(image), x, y, h * desiredRatio, h);
				} else {
					x = 0;	// width is the smaller dimension here
					y = Math.floor(h / 2 - (w * desiredRatio / 2));
					crop = async.apply(image.crop.bind(image), x, y, w, w * desiredRatio);
				}
			} else {
				// Simple resize given either width, height, or both
				crop = async.apply(setImmediate);
			}
 
			async.waterfall([
				crop,
				function (_image, next) {
					if (typeof _image === 'function' && !next) {
						next = _image;
						_image = image;
					}
 
					if ((data.width && data.height) || (w > data.width) || (h > data.height)) {
						_image.resize(data.width || Jimp.AUTO, data.height || Jimp.AUTO, next);
					} else {
						next(null, image);
					}
				},
				function (image, next) {
					image.write(data.target || data.path, next);
				}
			], function (err) {
				callback(err);
			});
		});
	}
};
 
image.normalise = function (path, extension, callback) {
	if (plugins.hasListeners('filter:image.normalise')) {
		plugins.fireHook('filter:image.normalise', {
			path: path,
			extension: extension
		}, function (err) {
			callback(err);
		});
	} else {
		new Jimp(path, function (err, image) {
			if (err) {
				return callback(err);
			}
			image.write(path + '.png', function (err) {
				callback(err);
			});
		});
	}
};
 
image.size = function (path, callback) {
	if (plugins.hasListeners('filter:image.size')) {
		plugins.fireHook('filter:image.size', {
			path: path,
		}, function (err, image) {
			callback(err, image);
		});
	} else {
		new Jimp(path, function (err, data) {
			callback(err, data ? data.bitmap : null);
		});
	}
};
 
image.convertImageToBase64 = function (path, callback) {
	fs.readFile(path, function (err, data) {
		callback(err, data ? data.toString('base64') : null);
	});
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/install.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/install.js

Statements: 8.94% (21 / 235)      Branches: 6.8% (10 / 147)      Functions: 0% (0 / 50)      Lines: 8.97% (21 / 234)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559    1                 1     1                                             1             1                                                                                   1                                                             1                                                                           1                                               1                           1                                 1                             1                                                                                                                                                                                                               1                                                   1                                                   1                           1                                                                 1                                                                                 1                                           1                                                                                   1                                             1    
'use strict';
 
var async = require('async'),
	fs = require('fs'),
	path = require('path'),
	prompt = require('prompt'),
	winston = require('winston'),
	nconf = require('nconf'),
	utils = require('../public/src/utils.js');
 
 
var install = {},
	questions = {};
 
questions.main = [
	{
		name: 'url',
		description: 'URL used to access this NodeBB',
		'default':
			nconf.get('url') ||
			(nconf.get('base_url') ? (nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '')) : null) ||	// backwards compatibility (remove for v0.7.0)
			'http://localhost:4567',
		pattern: /^http(?:s)?:\/\//,
		message: 'Base URL must begin with \'http://\' or \'https://\'',
	},
	{
		name: 'secret',
		description: 'Please enter a NodeBB secret',
		'default': nconf.get('secret') || utils.generateUUID()
	},
	{
		name: 'database',
		description: 'Which database to use',
		'default': nconf.get('database') || 'mongo'
	}
];
 
questions.optional = [
	{
		name: 'port',
		default: nconf.get('port') || 4567
	}
];
 
function checkSetupFlag(next) {
	var setupVal;
 
	try {
		if (nconf.get('setup')) {
			setupVal = JSON.parse(nconf.get('setup'));
		}
	} catch (err) {
		setupVal = undefined;
	}
 
	if (setupVal && setupVal instanceof Object) {
		if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:password:confirm'] && setupVal['admin:email']) {
			install.values = setupVal;
			next();
		} else {
			winston.error('Required values are missing for automated setup:');
			if (!setupVal['admin:username']) {
				winston.error('  admin:username');
			}
			if (!setupVal['admin:password']) {
				winston.error('  admin:password');
			}
			if (!setupVal['admin:password:confirm']) {
				winston.error('  admin:password:confirm');
			}
			if (!setupVal['admin:email']) {
				winston.error('  admin:email');
			}
 
			process.exit();
		}
	} else if (nconf.get('database')) {
		install.values = {
			database: nconf.get('database')
		};
		next();
	} else {
		next();
	}
}
 
function checkCIFlag(next) {
	var	ciVals;
	try {
		ciVals = JSON.parse(nconf.get('ci'));
	} catch (e) {
		ciVals = undefined;
	}
 
	if (ciVals && ciVals instanceof Object) {
		if (ciVals.hasOwnProperty('host') && ciVals.hasOwnProperty('port') && ciVals.hasOwnProperty('database')) {
			install.ciVals = ciVals;
			next();
		} else {
			winston.error('Required values are missing for automated CI integration:');
			if (!ciVals.hasOwnProperty('host')) {
				winston.error('  host');
			}
			if (!ciVals.hasOwnProperty('port')) {
				winston.error('  port');
			}
			if (!ciVals.hasOwnProperty('database')) {
				winston.error('  database');
			}
 
			process.exit();
		}
	} else {
		next();
	}
}
 
function setupConfig(next) {
	var configureDatabases = require('../install/databases');
 
	// prompt prepends "prompt: " to questions, let's clear that.
	prompt.start();
	prompt.message = '';
	prompt.delimiter = '';
	prompt.colors = false;
 
	if (!install.values) {
		prompt.get(questions.main, function (err, config) {
			if (err) {
				process.stdout.write('\n\n');
				winston.warn('NodeBB setup ' + err.message);
				process.exit();
			}
 
			configureDatabases(config, function (err, config) {
				completeConfigSetup(err, config, next);
			});
		});
	} else {
		// Use provided values, fall back to defaults
		var	config = {},
			redisQuestions = require('./database/redis').questions,
			mongoQuestions = require('./database/mongo').questions,
			allQuestions = questions.main.concat(questions.optional).concat(redisQuestions).concat(mongoQuestions);
 
		allQuestions.forEach(function (question) {
			config[question.name] = install.values[question.name] || question['default'] || undefined;
		});
 
		configureDatabases(config, function (err, config) {
			completeConfigSetup(err, config, next);
		});
	}
}
 
function completeConfigSetup(err, config, next) {
	if (err) {
		return next(err);
	}
 
	// Add CI object
	if (install.ciVals) {
		config.test_database = {};
		for(var prop in install.ciVals) {
			if (install.ciVals.hasOwnProperty(prop)) {
				config.test_database[prop] = install.ciVals[prop];
			}
		}
	}
 
	install.save(config, function (err) {
		if (err) {
			return next(err);
		}
 
		require('./database').init(next);
	});
}
 
function setupDefaultConfigs(next) {
	process.stdout.write('Populating database with default configs, if not already set...\n');
	var meta = require('./meta');
	var defaults = require(path.join(__dirname, '../', 'install/data/defaults.json'));
 
	meta.configs.setOnEmpty(defaults, function (err) {
		if (err) {
			return next(err);
		}
 
		meta.configs.init(next);
	});
}
 
function enableDefaultTheme(next) {
	var	meta = require('./meta');
 
	meta.configs.get('theme:id', function (err, id) {
		if (err || id) {
			process.stdout.write('Previous theme detected, skipping enabling default theme\n');
			return next(err);
		}
		var defaultTheme = nconf.get('defaultTheme') || 'nodebb-theme-persona';
		process.stdout.write('Enabling default theme: ' + defaultTheme + '\n');
		meta.themes.set({
			type: 'local',
			id: defaultTheme
		}, next);
	});
}
 
function createAdministrator(next) {
	var Groups = require('./groups');
	Groups.getMemberCount('administrators', function (err, memberCount) {
		if (err) {
			return next(err);
		}
		if (memberCount > 0) {
			process.stdout.write('Administrator found, skipping Admin setup\n');
			next();
		} else {
			createAdmin(next);
		}
	});
}
 
function createAdmin(callback) {
	var User = require('./user'),
		Groups = require('./groups'),
		password;
 
	winston.warn('No administrators have been detected, running initial user setup\n');
 
	var questions = [{
			name: 'username',
			description: 'Administrator username',
			required: true,
			type: 'string'
		}, {
			name: 'email',
			description: 'Administrator email address',
			pattern: /.+@.+/,
			required: true
		}],
		passwordQuestions = [{
			name: 'password',
			description: 'Password',
			required: true,
			hidden: true,
			type: 'string'
		}, {
			name: 'password:confirm',
			description: 'Confirm Password',
			required: true,
			hidden: true,
			type: 'string'
		}],
		success = function (err, results) {
			if (err) {
				return callback(err);
			}
			if (!results) {
				return callback(new Error('aborted'));
			}
 
			if (results['password:confirm'] !== results.password) {
				winston.warn("Passwords did not match, please try again");
				return retryPassword(results);
			}
			var adminUid;
			async.waterfall([
				function (next) {
					User.create({username: results.username, password: results.password, email: results.email}, next);
				},
				function (uid, next) {
					adminUid = uid;
					Groups.join('administrators', uid, next);
				},
				function (next) {
					Groups.show('administrators', next);
				},
				function (next) {
					Groups.ownership.grant(adminUid, 'administrators', next);
				}
			], function (err) {
				if (err) {
					return callback(err);
				}
				callback(null, password ? results : undefined);
			});
		},
		retryPassword = function (originalResults) {
			// Ask only the password questions
			prompt.get(passwordQuestions, function (err, results) {
				if (!results) {
					return callback(new Error('aborted'));
				}
 
				// Update the original data with newly collected password
				originalResults.password = results.password;
				originalResults['password:confirm'] = results['password:confirm'];
 
				// Send back to success to handle
				success(err, originalResults);
			});
		};
 
	// Add the password questions
	questions = questions.concat(passwordQuestions);
 
	if (!install.values) {
		prompt.get(questions, success);
	} else {
		// If automated setup did not provide a user password, generate one, it will be shown to the user upon setup completion
		if (!install.values.hasOwnProperty('admin:password') && !nconf.get('admin:password')) {
			process.stdout.write('Password was not provided during automated setup, generating one...\n');
			password = utils.generateUUID().slice(0, 8);
		}
 
		var results = {
			username: install.values['admin:username'] || nconf.get('admin:username') || 'admin',
			email: install.values['admin:email'] || nconf.get('admin:email') || '',
			password: install.values['admin:password'] || nconf.get('admin:password') || password,
			'password:confirm': install.values['admin:password:confirm'] || nconf.get('admin:password') || password
		};
 
		success(null, results);
	}
}
 
function createGlobalModeratorsGroup(next) {
	var groups = require('./groups');
	async.waterfall([
		function (next) {
			groups.exists('Global Moderators', next);
		},
		function (exists, next) {
			if (exists) {
				winston.info('Global Moderators group found, skipping creation!');
				return next(null, null);
			}
			groups.create({
				name: 'Global Moderators',
				userTitle: 'Global Moderator',
				description: 'Forum wide moderators',
				hidden: 0,
				private: 1,
				disableJoinRequests: 1
			}, next);
		},
		function (groupData, next) {
			groups.show('Global Moderators', next);
		}
	], next);
}
 
function createCategories(next) {
	var Categories = require('./categories');
 
	Categories.getAllCategories(0, function (err, categoryData) {
		if (err) {
			return next(err);
		}
 
		if (Array.isArray(categoryData) && categoryData.length) {
			process.stdout.write('Categories OK. Found ' + categoryData.length + ' categories.\n');
			return next();
		}
 
		process.stdout.write('No categories found, populating instance with default categories\n');
 
		fs.readFile(path.join(__dirname, '../', 'install/data/categories.json'), function (err, default_categories) {
			if (err) {
				return next(err);
			}
			default_categories = JSON.parse(default_categories);
 
			async.eachSeries(default_categories, Categories.create, next);
		});
	});
}
 
function createMenuItems(next) {
	var db = require('./database');
 
	db.exists('navigation:enabled', function (err, exists) {
		if (err || exists) {
			return next(err);
		}
		var navigation = require('./navigation/admin'),
			data = require('../install/data/navigation.json');
 
		navigation.save(data, next);
	});
}
 
function createWelcomePost(next) {
	var db = require('./database'),
		Topics = require('./topics');
 
	async.parallel([
		function (next) {
			fs.readFile(path.join(__dirname, '../', 'install/data/welcome.md'), next);
		},
		function (next) {
			db.getObjectField('global', 'topicCount', next);
		}
	], function (err, results) {
		if (err) {
			return next(err);
		}
 
		var content = results[0],
			numTopics = results[1];
 
		if (!parseInt(numTopics, 10)) {
			process.stdout.write('Creating welcome post!\n');
			Topics.post({
				uid: 1,
				cid: 2,
				title: 'Welcome to your NodeBB!',
				content: content.toString()
			}, next);
		} else {
			next();
		}
	});
}
 
function enableDefaultPlugins(next) {
 
	process.stdout.write('Enabling default plugins\n');
 
	var defaultEnabled = [
			'nodebb-plugin-composer-default',
			'nodebb-plugin-markdown',
			'nodebb-plugin-mentions',
			'nodebb-widget-essentials',
			'nodebb-rewards-essentials',
			'nodebb-plugin-soundpack-default',
			'nodebb-plugin-emoji-extended',
			'nodebb-plugin-emoji-one'
		],
		customDefaults = nconf.get('defaultPlugins');
 
	winston.info('[install/defaultPlugins] customDefaults', customDefaults);
 
	if (customDefaults && customDefaults.length) {
		try {
			customDefaults = JSON.parse(customDefaults);
			defaultEnabled = defaultEnabled.concat(customDefaults);
		} catch (e) {
			// Invalid value received
			winston.warn('[install/enableDefaultPlugins] Invalid defaultPlugins value received. Ignoring.');
		}
	}
 
	defaultEnabled = defaultEnabled.filter(function (plugin, index, array) {
		return array.indexOf(plugin) === index;
	});
 
	winston.info('[install/enableDefaultPlugins] activating default plugins', defaultEnabled);
 
	var db = require('./database');
	var order = defaultEnabled.map(function (plugin, index) {
		return index;
	});
	db.sortedSetAdd('plugins:active', order, defaultEnabled, next);
}
 
function setCopyrightWidget(next) {
	var	db = require('./database');
	async.parallel({
		footerJSON: function (next) {
			fs.readFile(path.join(__dirname, '../', 'install/data/footer.json'), next);
		},
		footer: function (next) {
			db.getObjectField('widgets:global', 'footer', next);
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
 
		if (!results.footer && results.footerJSON) {
			db.setObjectField('widgets:global', 'footer', results.footerJSON.toString(), next);
		} else {
			next();
		}
	});
}
 
install.setup = function (callback) {
 
 
	async.series([
		checkSetupFlag,
		checkCIFlag,
		setupConfig,
		setupDefaultConfigs,
		enableDefaultTheme,
		createCategories,
		createAdministrator,
		createGlobalModeratorsGroup,
		createMenuItems,
		createWelcomePost,
		enableDefaultPlugins,
		setCopyrightWidget,
		function (next) {
			var upgrade = require('./upgrade');
			upgrade.check(function (err, uptodate) {
				if (err) {
					return next(err);
				}
				if (!uptodate) { upgrade.upgrade(next); }
				else { next(); }
			});
		}
	], function (err, results) {
		if (err) {
			winston.warn('NodeBB Setup Aborted.\n ' + err.stack);
			process.exit();
		} else {
			var data = {};
			if (results[6]) {
				data.username = results[6].username;
				data.password = results[6].password;
			}
 
			callback(null, data);
		}
	});
};
 
install.save = function (server_conf, callback) {
	var	serverConfigPath = path.join(__dirname, '../config.json');
 
	if (nconf.get('config')) {
		serverConfigPath = path.resolve(__dirname, '../', nconf.get('config'));
	}
 
	fs.writeFile(serverConfigPath, JSON.stringify(server_conf, null, 4), function (err) {
		if (err) {
			winston.error('Error saving server configuration! ' + err.message);
			return callback(err);
		}
 
		process.stdout.write('Configuration Saved OK\n');
 
		nconf.file({
			file: path.join(__dirname, '..', 'config.json')
		});
 
		callback();
	});
};
 
module.exports = install;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/languages.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/languages.js

Statements: 9.8% (5 / 51)      Branches: 0% (0 / 28)      Functions: 0% (0 / 10)      Lines: 9.8% (5 / 51)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100    6 6 6 6   6                                                                                                                                                                                        
'use strict';
 
var fs = require('fs');
var path = require('path');
var async = require('async');
var LRU = require('lru-cache');
 
var plugins = require('./plugins');
 
var Languages = {};
var	languagesPath = path.join(__dirname, '../public/language');
 
Languages.init = function (next) {
	if (Languages.hasOwnProperty('_cache')) {
		Languages._cache.reset();
	} else {
		Languages._cache = LRU(100);
	}
 
	next();
};
 
Languages.get = function (language, namespace, callback) {
	var langNamespace = language + '/' + namespace;
 
	if (Languages._cache && Languages._cache.has(langNamespace)) {
		return callback(null, Languages._cache.get(langNamespace));
	}
 
	var languageData;
 
	fs.readFile(path.join(languagesPath, language, namespace + '.json'), { encoding: 'utf-8' }, function (err, data) {
		if (err && err.code !== 'ENOENT') {
			return callback(err);
		}
 
		// If language file in core cannot be read, then no language file present
		try {
			languageData = JSON.parse(data) || {};
		} catch (e) {
			languageData = {};
		}
 
		if (plugins.customLanguages.hasOwnProperty(langNamespace)) {
			Object.assign(languageData, plugins.customLanguages[langNamespace]);
		}
 
		if (Languages._cache) {
			Languages._cache.set(langNamespace, languageData);
		}
 
		callback(null, languageData);
	});
};
 
Languages.list = function (callback) {
	var languages = [];
 
	fs.readdir(languagesPath, function (err, files) {
		if (err) {
			return callback(err);
		}
 
		async.each(files, function (folder, next) {
			fs.stat(path.join(languagesPath, folder), function (err, stat) {
				if (err) {
					return next(err);
				}
 
				if (!stat.isDirectory()) {
					return next();
				}
 
				var configPath = path.join(languagesPath, folder, 'language.json');
 
				fs.readFile(configPath, function (err, stream) {
					if (err) {
						next();
					}
					languages.push(JSON.parse(stream.toString()));
					next();
				});
			});
		}, function (err) {
			if (err) {
				return callback(err);
			}
			// Sort alphabetically
			languages = languages.sort(function (a, b) {
				return a.code > b.code ? 1 : -1;
			});
 
			callback(err, languages);
		});
	});
};
 
module.exports = Languages;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/logger.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/logger.js

Statements: 2.25% (2 / 89)      Branches: 0% (0 / 56)      Functions: 0% (0 / 17)      Lines: 2.25% (2 / 89)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229            2                                                                                                                                                                                                                                                                                                                                                                                                   1                                                        
'use strict';
 
/*
 * Logger module: ability to dynamically turn on/off logging for http requests & socket.io events
 */
 
var fs = require('fs'),
	path = require('path'),
	winston = require('winston'),
	util = require('util'),
 
	file = require('./file'),
	meta = require('./meta'),
	morgan = require('morgan');
 
var opts = {
	/*
	 * state used by Logger
	 */
	express : {
		app : {},
		set : 0,
		ofn : null,
	},
	streams : {
		log : { f : process.stdout },
	}
};
 
 
/* -- Logger -- */
 
(function (Logger) {
 
 
	Logger.init = function (app) {
		opts.express.app = app;
		/* Open log file stream & initialize express logging if meta.config.logger* variables are set */
		Logger.setup();
	};
 
	Logger.setup = function () {
		Logger.setup_one('loggerPath', meta.config.loggerPath);
	};
 
	Logger.setup_one = function (key, value) {
		/*
		 * 1. Open the logger stream: stdout or file
		 * 2. Re-initialize the express logger hijack
		 */
		if (key === 'loggerPath') {
			Logger.setup_one_log(value);
			Logger.express_open();
		}
	};
 
	Logger.setup_one_log = function (value) {
		/*
		 * If logging is currently enabled, create a stream.
		 * Otherwise, close the current stream
		 */
		if(meta.config.loggerStatus > 0 || meta.config.loggerIOStatus) {
			var stream = Logger.open(value);
			if(stream) {
				opts.streams.log.f = stream;
			} else {
				opts.streams.log.f = process.stdout;
			}
		}
		else {
			Logger.close(opts.streams.log);
		}
	};
 
	Logger.open = function (value) {
		/* Open the streams to log to: either a path or stdout */
		var stream;
		if(value) {
			if(file.existsSync(value)) {
				var stats = fs.statSync(value);
				if(stats) {
					if(stats.isDirectory()) {
						stream = fs.createWriteStream(path.join(value, 'nodebb.log'), {flags: 'a'});
					} else {
						stream = fs.createWriteStream(value, {flags: 'a'});
					}
				}
			} else {
				stream = fs.createWriteStream(value, {flags: 'a'});
 
			}
 
			if(stream) {
				stream.on('error', function (err) {
					winston.error(err.message);
				});
			}
		} else {
			stream = process.stdout;
		}
		return stream;
	};
 
	Logger.close = function (stream) {
		if(stream.f !== process.stdout && stream.f) {
			stream.end();
		}
		stream.f = null;
	};
 
	Logger.monitorConfig = function (socket, data) {
		/*
		 * This monitor's when a user clicks "save" in the Logger section of the admin panel
		 */
		Logger.setup_one(data.key,data.value);
		Logger.io_close(socket);
		Logger.io(socket);
	};
 
	Logger.express_open = function () {
		if(opts.express.set !== 1) {
			opts.express.set = 1;
			opts.express.app.use(Logger.expressLogger);
		}
		/*
		 * Always initialize "ofn" (original function) with the original logger function
		 */
		opts.express.ofn = morgan('combined', {stream : opts.streams.log.f});
	};
 
	Logger.expressLogger = function (req,res,next) {
		/*
		 * The new express.logger
		 *
		 * This hijack allows us to turn logger on/off dynamically within express
		 */
		if(meta.config.loggerStatus > 0) {
			return opts.express.ofn(req,res,next);
		} else {
			return next();
		}
	};
 
	Logger.prepare_io_string = function (_type, _uid, _args) {
		/*
		 * This prepares the output string for intercepted socket.io events
		 *
		 * The format is: io: <uid> <event> <args>
		 */
		try {
			return 'io: ' + _uid + ' ' + _type + ' ' + util.inspect(Array.prototype.slice.call(_args)) + '\n';
		} catch(err) {
			winston.info("Logger.prepare_io_string: Failed", err);
			return "error";
		}
	};
 
	Logger.io_close = function (socket) {
		/*
		 * Restore all hijacked sockets to their original emit/on functions
		 */
		if (!socket || !socket.io || !socket.io.sockets || !socket.io.sockets.sockets) {
			return;
		}
		var clients = socket.io.sockets.sockets;
		for (var sid in clients) {
			if (clients.hasOwnProperty(sid)) {
				var client = clients[sid];
				if(client.oEmit && client.oEmit !== client.emit) {
					client.emit = client.oEmit;
				}
 
				if(client.$oEmit && client.$oEmit !== client.$emit) {
					client.$emit = client.$oEmit;
				}
			}
		}
	};
 
	Logger.io = function (socket) {
		/*
		 * Go through all of the currently established sockets & hook their .emit/.on
		 */
 
		if (!socket || !socket.io || !socket.io.sockets || !socket.io.sockets.sockets) {
			return;
		}
 
		var clients = socket.io.sockets.sockets;
		for(var sid in clients) {
			if (clients.hasOwnProperty(sid)) {
				Logger.io_one(clients[sid], clients[sid].uid);
			}
		}
	};
 
	Logger.io_one = function (socket, uid) {
		/*
		 * This function replaces a socket's .emit/.on functions in order to intercept events
		 */
		function override(method, name, errorMsg) {
			return function () {
				if(opts.streams.log.f) {
					opts.streams.log.f.write(Logger.prepare_io_string(name, uid, arguments));
				}
 
				try {
					method.apply(socket, arguments);
				} catch(err) {
					winston.info(errorMsg, err);
				}
			};
		}
 
		if (socket && meta.config.loggerIOStatus > 0) {
			// courtesy of: http://stackoverflow.com/a/9674248
			socket.oEmit = socket.emit;
			var emit = socket.emit;
			socket.emit = override(emit, 'emit', 'Logger.io_one: emit.apply: Failed');
 
			socket.$oEmit = socket.$emit;
			var $emit = socket.$emit;
			socket.$emit = override($emit, 'on', 'Logger.io_one: $emit.apply: Failed');
		}
	};
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging.js

Statements: 2.65% (6 / 226)      Branches: 0% (0 / 128)      Functions: 0% (0 / 80)      Lines: 2.65% (6 / 226)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482      4 4 4   4                                                                                                                                                     1                                                                                                                                                                                                                                                                                                                                                                                                                                                                           1                                                                                                                                                                                                                                                                                                                                                  
'use strict';
 
 
var async = require('async');
var winston = require('winston');
var S = require('string');
 
var db = require('./database');
var user = require('./user');
var plugins = require('./plugins');
var meta = require('./meta');
var utils = require('../public/src/utils');
var notifications = require('./notifications');
var userNotifications = require('./user/notifications');
 
(function (Messaging) {
 
	require('./messaging/create')(Messaging);
	require('./messaging/delete')(Messaging);
	require('./messaging/edit')(Messaging);
	require('./messaging/rooms')(Messaging);
	require('./messaging/unread')(Messaging);
	require('./messaging/notifications')(Messaging);
 
	Messaging.getMessageField = function (mid, field, callback) {
		Messaging.getMessageFields(mid, [field], function (err, fields) {
			callback(err, fields ? fields[field] : null);
		});
	};
 
	Messaging.getMessageFields = function (mid, fields, callback) {
		db.getObjectFields('message:' + mid, fields, callback);
	};
 
	Messaging.setMessageField = function (mid, field, content, callback) {
		db.setObjectField('message:' + mid, field, content, callback);
	};
 
	Messaging.setMessageFields = function (mid, data, callback) {
		db.setObject('message:' + mid, data, callback);
	};
 
	Messaging.getMessages = function (params, callback) {
		var uid = params.uid;
		var roomId = params.roomId;
		var isNew = params.isNew || false;
		var start = params.hasOwnProperty('start') ? params.start : 0;
		var stop = parseInt(start, 10) + ((params.count || 50) - 1);
 
		var indices = {};
		async.waterfall([
			function (next) {
				canGetMessages(params.callerUid, params.uid, next);
			},
			function (canGet, next) {
				if (!canGet) {
					return callback(null, null);
				}
				db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', start, stop, next);
			},
			function (mids, next) {
				if (!Array.isArray(mids) || !mids.length) {
					return callback(null, []);
				}
 
				mids.forEach(function (mid, index) {
					indices[mid] = start + index;
				});
 
				mids.reverse();
 
				Messaging.getMessagesData(mids, uid, roomId, isNew, next);
			},
			function (messageData, next) {
				messageData.forEach(function (messageData) {
					messageData.index = indices[messageData.messageId.toString()];
				});
				next(null, messageData);
			}
		], callback);
	};
 
	function canGetMessages(callerUid, uid, callback) {
		plugins.fireHook('filter:messaging.canGetMessages', {
			callerUid: callerUid,
			uid: uid,
			canGet: parseInt(callerUid, 10) === parseInt(uid, 10)
		}, function (err, data) {
			callback(err, data ? data.canGet : false);
		});
	}
 
	Messaging.getMessagesData = function (mids, uid, roomId, isNew, callback) {
 
		var keys = mids.map(function (mid) {
			return 'message:' + mid;
		});
 
		var messages;
 
		async.waterfall([
			function (next) {
				db.getObjects(keys, next);
			},
			function (_messages, next) {
				messages = _messages.map(function (msg, idx) {
					if (msg) {
						msg.messageId = parseInt(mids[idx], 10);
					}
					return msg;
				}).filter(Boolean);
 
				var uids = messages.map(function (msg) {
					return msg && msg.fromuid;
				});
 
				user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], next);
			},
			function (users, next) {
				messages.forEach(function (message, index) {
					message.fromUser = users[index];
					var self = parseInt(message.fromuid, 10) === parseInt(uid, 10);
					message.self = self ? 1 : 0;
					message.timestampISO = utils.toISOString(message.timestamp);
					message.newSet = false;
					message.roomId = String(message.roomId || roomId);
					if (message.hasOwnProperty('edited')) {
						message.editedISO = new Date(parseInt(message.edited, 10)).toISOString();
					}
				});
 
				async.map(messages, function (message, next) {
					Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function (err, result) {
						if (err) {
							return next(err);
						}
						message.content = result;
						message.cleanedContent = S(result).stripTags().decodeHTMLEntities().s;
						next(null, message);
					});
				}, next);
			},
			function (messages, next) {
				if (messages.length > 1) {
					// Add a spacer in between messages with time gaps between them
					messages = messages.map(function (message, index) {
						// Compare timestamps with the previous message, and check if a spacer needs to be added
						if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index - 1].timestamp, 10) + (1000 * 60 * 5)) {
							// If it's been 5 minutes, this is a new set of messages
							message.newSet = true;
						} else if (index > 0 && message.fromuid !== messages[index - 1].fromuid) {
							// If the previous message was from the other person, this is also a new set
							message.newSet = true;
						}
 
						return message;
					});
 
					next(undefined, messages);
				} else if (messages.length === 1) {
					// For single messages, we don't know the context, so look up the previous message and compare
					var key = 'uid:' + uid + ':chat:room:' + roomId + ':mids';
					async.waterfall([
						async.apply(db.sortedSetRank, key, messages[0].messageId),
						function (index, next) {
							// Continue only if this isn't the first message in sorted set
							if (index > 0) {
								db.getSortedSetRange(key, index - 1, index - 1, next);
							} else {
								messages[0].newSet = true;
								return next(undefined, messages);
							}
						},
						function (mid, next) {
							Messaging.getMessageFields(mid, ['fromuid', 'timestamp'], next);
						}
					], function (err, fields) {
						if (err) {
							return next(err);
						}
 
						if (
							(parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + (1000 * 60 * 5)) ||
							(parseInt(messages[0].fromuid, 10) !== parseInt(fields.fromuid, 10))
						) {
							// If it's been 5 minutes, this is a new set of messages
							messages[0].newSet = true;
						}
 
						next(undefined, messages);
					});
				} else {
					next(null, []);
				}
			}
		], callback);
 
	};
 
	Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) {
		plugins.fireHook('filter:parse.raw', message, function (err, parsed) {
			if (err) {
				return callback(err);
			}
 
			var messageData = {
				message: message,
				parsed: parsed,
				fromuid: fromuid,
				uid: uid,
				roomId: roomId,
				isNew: isNew,
				parsedMessage: parsed
			};
 
			plugins.fireHook('filter:messaging.parse', messageData, function (err, messageData) {
				callback(err, messageData ? messageData.parsedMessage : '');
			});
		});
	};
 
	Messaging.isNewSet = function (uid, roomId, timestamp, callback) {
		var setKey = 'uid:' + uid + ':chat:room:' + roomId + ':mids';
 
		async.waterfall([
			function (next) {
				db.getSortedSetRevRangeWithScores(setKey, 0, 0, next);
			},
			function (messages, next) {
				if (messages && messages.length) {
					next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + (1000 * 60 * 5));
				} else {
					next(null, true);
				}
			}
		], callback);
	};
 
 
	Messaging.getRecentChats = function (callerUid, uid, start, stop, callback) {
		async.waterfall([
			function (next) {
				canGetRecentChats(callerUid, uid, next);
			},
			function (canGet, next) {
				if (!canGet) {
					return callback(null, null);
				}
				db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, next);
			},
			function (roomIds, next) {
				async.parallel({
					roomData: function (next) {
						Messaging.getRoomsData(roomIds, next);
					},
					unread: function (next) {
						db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next);
					},
					users: function (next) {
						async.map(roomIds, function (roomId, next) {
							db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9, function (err, uids) {
								if (err) {
									return next(err);
								}
								uids = uids.filter(function (value) {
									return value && parseInt(value, 10) !== parseInt(uid, 10);
								});
								user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next);
							});
						}, next);
					},
					teasers: function (next) {
						async.map(roomIds, function (roomId, next) {
							Messaging.getTeaser(uid, roomId, next);
						}, next);
					}
				}, next);
			},
			function (results, next) {
				results.roomData.forEach(function (room, index) {
					room.users = results.users[index];
					room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2;
					room.unread = results.unread[index];
					room.teaser = results.teasers[index];
 
					room.users.forEach(function (userData) {
						if (userData && parseInt(userData.uid, 10)) {
							userData.status = user.getStatus(userData);
						}
					});
					room.users = room.users.filter(function (user) {
						return user && parseInt(user.uid, 10);
					});
					room.lastUser = room.users[0];
 
					room.usernames = Messaging.generateUsernames(room.users, uid);
				});
 
				next(null, {rooms: results.roomData, nextStart: stop + 1});
			}
		], callback);
	};
 
	Messaging.generateUsernames = function (users, excludeUid) {
		users = users.filter(function (user) {
			return user && parseInt(user.uid, 10) !== excludeUid;
		});
		return users.map(function (user) {
			return user.username;
		}).join(', ');
	};
 
	function canGetRecentChats(callerUid, uid, callback) {
		plugins.fireHook('filter:messaging.canGetRecentChats', {
			callerUid: callerUid,
			uid: uid,
			canGet: parseInt(callerUid, 10) === parseInt(uid, 10)
		}, function (err, data) {
			callback(err, data ? data.canGet : false);
		});
	}
 
	Messaging.getTeaser = function (uid, roomId, callback) {
		var teaser;
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', 0, 0, next);
			},
			function (mids, next) {
				if (!mids || !mids.length) {
					return next(null, null);
				}
				Messaging.getMessageFields(mids[0], ['fromuid', 'content', 'timestamp'], next);
			},
			function (_teaser, next) {
				teaser = _teaser;
				if (!teaser) {
					return callback();
				}
				if (teaser.content) {
					teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s;
				}
 
				teaser.timestampISO = utils.toISOString(teaser.timestamp);
				user.getUserFields(teaser.fromuid, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next);
			},
			function (user, next) {
				teaser.user = user;
				next(null, teaser);
			}
		], callback);
	};
 
	Messaging.canMessageUser = function (uid, toUid, callback) {
		if (parseInt(meta.config.disableChat) === 1 || !uid || uid === toUid) {
			return callback(new Error('[[error:chat-disabled]]'));
		}
 
		if (parseInt(uid, 10) === parseInt(toUid, 10)) {
			return callback(new Error('[[error:cant-chat-with-yourself'));
		}
 
		async.waterfall([
			function (next) {
				user.exists(toUid, next);
			},
			function (exists, next) {
				if (!exists) {
					return callback(new Error('[[error:no-user]]'));
				}
				user.getUserFields(uid, ['banned', 'email:confirmed'], next);
			},
			function (userData, next) {
				if (parseInt(userData.banned, 10) === 1) {
					return callback(new Error('[[error:user-banned]]'));
				}
 
				if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) {
					return callback(new Error('[[error:email-not-confirmed-chat]]'));
				}
 
				async.parallel({
					settings: async.apply(user.getSettings, toUid),
					isAdmin: async.apply(user.isAdministrator, uid),
					isFollowing: async.apply(user.isFollowing, toUid, uid)
				}, next);
			},
			function (results, next) {
				if (!results.settings.restrictChat || results.isAdmin || results.isFollowing) {
					return next();
				}
 
 				next(new Error('[[error:chat-restricted]]'));
			}
		], callback);
	};
 
	Messaging.canMessageRoom = function (uid, roomId, callback) {
		if (parseInt(meta.config.disableChat) === 1 || !uid) {
			return callback(new Error('[[error:chat-disabled]]'));
		}
 
		async.waterfall([
			function (next) {
				Messaging.isUserInRoom(uid, roomId, next);
			},
			function (inRoom, next) {
				if (!inRoom) {
					return next(new Error('[[error:not-in-room]]'));
				}
 
				Messaging.getUserCountInRoom(roomId, next);
			},
			function (count, next) {
				if (count < 2) {
					return next(new Error('[[error:no-users-in-room]]'));
				}
 
				user.getUserFields(uid, ['banned', 'email:confirmed'], next);
			},
			function (userData, next) {
				if (parseInt(userData.banned, 10) === 1) {
					return next(new Error('[[error:user-banned]]'));
				}
 
				if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) {
					return next(new Error('[[error:email-not-confirmed-chat]]'));
				}
 
				next();
			}
		], callback);
	};
 
	Messaging.hasPrivateChat = function (uid, withUid, callback) {
		if (parseInt(uid, 10) === parseInt(withUid, 10)) {
			return callback(null, 0);
		}
		async.waterfall([
			function (next) {
				async.parallel({
					myRooms: async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':chat:rooms', 0, -1),
					theirRooms: async.apply(db.getSortedSetRevRange, 'uid:' + withUid + ':chat:rooms', 0, -1)
				}, next);
			},
			function (results, next) {
				var roomIds = results.myRooms.filter(function (roomId) {
					return roomId && results.theirRooms.indexOf(roomId) !== -1;
				});
 
				if (!roomIds.length) {
					return callback();
				}
 
				var index = 0;
				var roomId = 0;
				async.whilst(function () {
					return index < roomIds.length && !roomId;
				}, function (next) {
					Messaging.getUserCountInRoom(roomIds[index], function (err, count) {
						if (err) {
							return next(err);
						}
						if (count === 2) {
							roomId = roomIds[index];
							next(null, roomId);
						} else {
							++ index;
							next();
						}
					});
				}, function (err) {
					next(err, roomId);
				});
			}
		], callback);
	};
 
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta.js

Statements: 24.39% (10 / 41)      Branches: 0% (0 / 8)      Functions: 12.5% (1 / 8)      Lines: 25% (10 / 40)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72    72 72 72 72   72 72   72 72   72                                                                                             1                      
"use strict";
 
var async = require('async');
var winston = require('winston');
var os = require('os');
var nconf = require('nconf');
 
var pubsub = require('./pubsub');
var utils = require('../public/src/utils');
 
(function (Meta) {
	Meta.reloadRequired = false;
 
	require('./meta/configs')(Meta);
	require('./meta/themes')(Meta);
	require('./meta/js')(Meta);
	require('./meta/css')(Meta);
	require('./meta/sounds')(Meta);
	require('./meta/settings')(Meta);
	require('./meta/logs')(Meta);
	require('./meta/errors')(Meta);
	require('./meta/tags')(Meta);
	require('./meta/dependencies')(Meta);
	Meta.templates = require('./meta/templates');
	Meta.blacklist = require('./meta/blacklist');
 
	/* Assorted */
	Meta.userOrGroupExists = function (slug, callback) {
		var user = require('./user');
		var groups = require('./groups');
		slug = utils.slugify(slug);
		async.parallel([
			async.apply(user.existsBySlug, slug),
			async.apply(groups.existsBySlug, slug)
		], function (err, results) {
			callback(err, results ? results.some(function (result) { return result; }) : false);
		});
	};
 
	/**
	 * Reload deprecated as of v1.1.2+, remove in v2.x
	 */
	Meta.reload = function (callback) {
		restart();
		callback();
	};
 
	Meta.restart = function () {
		pubsub.publish('meta:restart', {hostname: os.hostname()});
		restart();
	};
 
	if (nconf.get('isPrimary') === 'true') {
		pubsub.on('meta:restart', function (data) {
			if (data.hostname !== os.hostname()) {
				restart();
			}
		});
	}
 
	function restart() {
		if (process.send) {
			process.send({
				action: 'restart'
			});
		} else {
			winston.error('[meta.restart] Could not restart, are you sure NodeBB was started with `./nodebb start`?');
		}
	}
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/notifications.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/notifications.js

Statements: 3.2% (8 / 250)      Branches: 0% (0 / 164)      Functions: 0% (0 / 86)      Lines: 3.2% (8 / 250)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513    2 2 2 2 2 2   2                                                                                                                                                                                                                                                                                                                                                             1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
'use strict';
 
var async = require('async');
var winston = require('winston');
var cron = require('cron').CronJob;
var nconf = require('nconf');
var S = require('string');
var _ = require('underscore');
 
var db = require('./database');
var User = require('./user');
var groups = require('./groups');
var meta = require('./meta');
var batch = require('./batch');
var plugins = require('./plugins');
var utils = require('../public/src/utils');
 
(function (Notifications) {
 
	Notifications.init = function () {
		winston.verbose('[notifications.init] Registering jobs.');
		new cron('*/30 * * * *', Notifications.prune, null, true);
	};
 
	Notifications.get = function (nid, callback) {
		Notifications.getMultiple([nid], function (err, notifications) {
			callback(err, Array.isArray(notifications) && notifications.length ? notifications[0] : null);
		});
	};
 
	Notifications.getMultiple = function (nids, callback) {
		var keys = nids.map(function (nid) {
			return 'notifications:' + nid;
		});
 
		db.getObjects(keys, function (err, notifications) {
			if (err) {
				return callback(err);
			}
 
			notifications = notifications.filter(Boolean);
			if (!notifications.length) {
				return callback(null, []);
			}
 
			var userKeys = notifications.map(function (notification) {
				return notification.from;
			});
 
			User.getUsersFields(userKeys, ['username', 'userslug', 'picture'], function (err, usersData) {
				if (err) {
					return callback(err);
				}
				notifications.forEach(function (notification, index) {
					notification.datetimeISO = utils.toISOString(notification.datetime);
 
					if (notification.bodyLong) {
						notification.bodyLong = S(notification.bodyLong).escapeHTML().s;
					}
 
					notification.user = usersData[index];
					if (notification.user) {
						notification.image = notification.user.picture || null;
						if (notification.user.username === '[[global:guest]]') {
							notification.bodyShort = notification.bodyShort.replace(/([\s\S]*?),[\s\S]*?,([\s\S]*?)/, '$1, [[global:guest]], $2');
						}
					} else if (notification.image === 'brand:logo' || !notification.image) {
						notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png';
					}
				});
 
				callback(null, notifications);
			});
		});
	};
 
	Notifications.filterExists = function (nids, callback) {
		// Removes nids that have been pruned
		db.isSortedSetMembers('notifications', nids, function (err, exists) {
			if (err) {
				return callback(err);
			}
 
			nids = nids.filter(function (notifId, idx) {
				return exists[idx];
			});
 
			callback(null, nids);
		});
	};
 
	Notifications.findRelated = function (mergeIds, set, callback) {
		// A related notification is one in a zset that has the same mergeId
		var _nids;
 
		async.waterfall([
			async.apply(db.getSortedSetRevRange, set, 0, -1),
			function (nids, next) {
				_nids = nids;
 
				var keys = nids.map(function (nid) {
					return 'notifications:' + nid;
				});
 
				db.getObjectsFields(keys, ['mergeId'], next);
			},
		], function (err, sets) {
			if (err) {
				return callback(err);
			}
 
			sets = sets.map(function (set) {
				return set.mergeId;
			});
 
			callback(null, _nids.filter(function (nid, idx) {
				return mergeIds.indexOf(sets[idx]) !== -1;
			}));
		});
	};
 
	Notifications.create = function (data, callback) {
		if (!data.nid) {
			return callback(new Error('no-notification-id'));
		}
		data.importance = data.importance || 5;
		db.getObject('notifications:' + data.nid, function (err, oldNotification) {
			if (err) {
				return callback(err);
			}
 
			if (oldNotification) {
				if (parseInt(oldNotification.pid, 10) === parseInt(data.pid, 10) && parseInt(oldNotification.importance, 10) > parseInt(data.importance, 10)) {
					return callback(null, null);
				}
			}
 
			var now = Date.now();
			data.datetime = now;
			async.parallel([
				function (next) {
					db.sortedSetAdd('notifications', now, data.nid, next);
				},
				function (next) {
					db.setObject('notifications:' + data.nid, data, next);
				}
			], function (err) {
				callback(err, data);
			});
		});
	};
 
	Notifications.push = function (notification, uids, callback) {
		callback = callback || function () {};
 
		if (!notification || !notification.nid) {
			return callback();
		}
 
		if (!Array.isArray(uids)) {
			uids = [uids];
		}
 
		uids = uids.filter(function (uid, index, array) {
			return parseInt(uid, 10) && array.indexOf(uid) === index;
		});
 
		if (!uids.length) {
			return callback();
		}
 
		setTimeout(function () {
			batch.processArray(uids, function (uids, next) {
				pushToUids(uids, notification, next);
			}, {interval: 1000}, function (err) {
				if (err) {
					winston.error(err.stack);
				}
			});
		}, 1000);
 
		callback();
	};
 
	function pushToUids(uids, notification, callback) {
		var oneWeekAgo = Date.now() - 604800000;
		var unreadKeys = [];
		var readKeys = [];
 
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:notification.push', {notification: notification, uids: uids}, next);
			},
			function (data, next) {
				uids = data.uids;
				notification = data.notification;
 
				uids.forEach(function (uid) {
					unreadKeys.push('uid:' + uid + ':notifications:unread');
					readKeys.push('uid:' + uid + ':notifications:read');
				});
 
				db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next);
			},
			function (next) {
				db.sortedSetsRemove(readKeys, notification.nid, next);
			},
			function (next) {
				db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next);
			},
			function (next) {
				db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next);
			},
			function (next) {
				var websockets = require('./socket.io');
				if (websockets.server) {
					uids.forEach(function (uid) {
						websockets.in('uid_' + uid).emit('event:new_notification', notification);
					});
				}
 
				plugins.fireHook('action:notification.pushed', {notification: notification, uids: uids});
				next();
			}
		], callback);
	}
 
	Notifications.pushGroup = function (notification, groupName, callback) {
		callback = callback || function () {};
		groups.getMembers(groupName, 0, -1, function (err, members) {
			if (err || !Array.isArray(members) || !members.length) {
				return callback(err);
			}
 
			Notifications.push(notification, members, callback);
		});
	};
 
	Notifications.pushGroups = function (notification, groupNames, callback) {
		callback = callback || function () {};
		groups.getMembersOfGroups(groupNames, function (err, groupMembers) {
			if (err) {
				return callback(err);
			}
 
			var members = _.unique(_.flatten(groupMembers));
 
			Notifications.push(notification, members, callback);
		});
	};
 
	Notifications.rescind = function (nid, callback) {
		callback = callback || function () {};
 
		async.parallel([
			async.apply(db.sortedSetRemove, 'notifications', nid),
			async.apply(db.delete, 'notifications:' + nid)
		], function (err) {
			if (err) {
				winston.error('Encountered error rescinding notification (' + nid + '): ' + err.message);
			} else {
				winston.verbose('[notifications/rescind] Rescinded notification "' + nid + '"');
			}
 
			callback(err, nid);
		});
	};
 
	Notifications.markRead = function (nid, uid, callback) {
		callback = callback || function () {};
		if (!parseInt(uid, 10) || !nid) {
			return callback();
		}
		Notifications.markReadMultiple([nid], uid, callback);
	};
 
	Notifications.markUnread = function (nid, uid, callback) {
		callback = callback || function () {};
		if (!parseInt(uid, 10) || !nid) {
			return callback();
		}
 
		db.getObject('notifications:' + nid, function (err, notification) {
			if (err || !notification) {
				return callback(err || new Error('[[error:no-notification]]'));
			}
			notification.datetime = notification.datetime || Date.now();
 
			async.parallel([
				async.apply(db.sortedSetRemove, 'uid:' + uid + ':notifications:read', nid),
				async.apply(db.sortedSetAdd, 'uid:' + uid + ':notifications:unread', notification.datetime, nid)
			], callback);
		});
	};
 
	Notifications.markReadMultiple = function (nids, uid, callback) {
		callback = callback || function () {};
		nids = nids.filter(Boolean);
		if (!Array.isArray(nids) || !nids.length) {
			return callback();
		}
 
		var notificationKeys = nids.map(function (nid) {
			return 'notifications:' + nid;
		});
 
		async.waterfall([
			async.apply(db.getObjectsFields, notificationKeys, ['mergeId']),
			function (mergeIds, next) {
				// Isolate mergeIds and find related notifications
				mergeIds = mergeIds.map(function (set) {
					return set.mergeId;
				}).reduce(function (memo, mergeId, idx, arr) {
					if (mergeId && idx === arr.indexOf(mergeId)) {
						memo.push(mergeId);
					}
					return memo;
				}, []);
 
				Notifications.findRelated(mergeIds, 'uid:' + uid + ':notifications:unread', next);
			},
			function (relatedNids, next) {
				notificationKeys = _.union(nids, relatedNids).map(function (nid) {
					return 'notifications:' + nid;
				});
 
				db.getObjectsFields(notificationKeys, ['nid', 'datetime'], next);
			}
		], function (err, notificationData) {
			if (err) {
				return callback(err);
			}
 
			// Filter out notifications that didn't exist
			notificationData = notificationData.filter(function (notification) {
				return notification && notification.nid;
			});
 
			// Extract nid
			nids = notificationData.map(function (notification) {
				return notification.nid;
			});
 
			var datetimes = notificationData.map(function (notification) {
				return (notification && notification.datetime) || Date.now();
			});
 
			async.parallel([
				function (next) {
					db.sortedSetRemove('uid:' + uid + ':notifications:unread', nids, next);
				},
				function (next) {
					db.sortedSetAdd('uid:' + uid + ':notifications:read', datetimes, nids, next);
				}
			], function (err) {
				callback(err);
			});
		});
	};
 
	Notifications.markAllRead = function (uid, callback) {
		db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, function (err, nids) {
			if (err) {
				return callback(err);
			}
 
			if (!Array.isArray(nids) || !nids.length) {
				return callback();
			}
 
			Notifications.markReadMultiple(nids, uid, callback);
		});
	};
 
	Notifications.prune = function () {
		var	week = 604800000,
			numPruned = 0;
 
		var	cutoffTime = Date.now() - week;
 
		db.getSortedSetRangeByScore('notifications', 0, 500, '-inf', cutoffTime, function (err, nids) {
			if (err) {
				return winston.error(err.message);
			}
 
			if (!Array.isArray(nids) || !nids.length) {
				return;
			}
 
			var	keys = nids.map(function (nid) {
				return 'notifications:' + nid;
			});
 
			numPruned = nids.length;
 
			async.parallel([
				function (next) {
					db.sortedSetRemove('notifications', nids, next);
				},
				function (next) {
					db.deleteAll(keys, next);
				}
			], function (err) {
				if (err) {
					return winston.error('Encountered error pruning notifications: ' + err.message);
				}
			});
		});
	};
 
	Notifications.merge = function (notifications, callback) {
		// When passed a set of notification objects, merge any that can be merged
		var mergeIds = [
				'notifications:upvoted_your_post_in',
				'notifications:user_started_following_you',
				'notifications:user_posted_to',
				'notifications:user_flagged_post_in',
				'new_register'
			],
			isolated, differentiators, differentiator, modifyIndex, set;
 
		notifications = mergeIds.reduce(function (notifications, mergeId) {
			isolated = notifications.filter(function (notifObj) {
				if (!notifObj || !notifObj.hasOwnProperty('mergeId')) {
					return false;
				}
 
				return notifObj.mergeId.split('|')[0] === mergeId;
			});
 
			if (isolated.length <= 1) {
				return notifications;	// Nothing to merge
			}
 
			// Each isolated mergeId may have multiple differentiators, so process each separately
			differentiators = isolated.reduce(function (cur, next) {
				differentiator = next.mergeId.split('|')[1] || 0;
				if (cur.indexOf(differentiator) === -1) {
					cur.push(differentiator);
				}
 
				return cur;
			}, []);
 
			differentiators.forEach(function (differentiator) {
				if (differentiator === 0 && differentiators.length === 1) {
					set = isolated;
				} else {
					set = isolated.filter(function (notifObj) {
						return notifObj.mergeId === (mergeId + '|' + differentiator);
					});
				}
 
				modifyIndex = notifications.indexOf(set[0]);
				if (modifyIndex === -1 || set.length === 1) {
					return notifications;
				}
 
				switch(mergeId) {
					// intentional fall-through
					case 'notifications:upvoted_your_post_in':
					case 'notifications:user_started_following_you':
					case 'notifications:user_posted_to':
					case 'notifications:user_flagged_post_in':
						var usernames = set.map(function (notifObj) {
							return notifObj && notifObj.user && notifObj.user.username;
						}).filter(function (username, idx, array) {
							return array.indexOf(username) === idx;
						});
						var numUsers = usernames.length;
 
						var title = S(notifications[modifyIndex].topicTitle || '').decodeHTMLEntities().s;
						var titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
						titleEscaped = titleEscaped ? (', ' + titleEscaped) : '';
 
						if (numUsers === 2) {
							notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + titleEscaped + ']]';
						} else if (numUsers > 2) {
							notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers - 1) + titleEscaped + ']]';
						}
 
						notifications[modifyIndex].path = set[set.length - 1].path;
						break;
 
					case 'new_register':
						notifications[modifyIndex].bodyShort = '[[notifications:' + mergeId + '_multiple, ' + set.length + ']]';
						break;
				}
 
				// Filter out duplicates
				notifications = notifications.filter(function (notifObj, idx) {
					if (!notifObj || !notifObj.mergeId) {
						return true;
					}
 
					return !(notifObj.mergeId === (mergeId + (differentiator ? '|' + differentiator : '')) && idx !== modifyIndex);
				});
			});
 
			return notifications;
		}, notifications);
 
		plugins.fireHook('filter:notifications.merge', {
			notifications: notifications
		}, function (err, data) {
			callback(err, data.notifications);
		});
	};
 
}(exports));
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/pagination.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/pagination.js

Statements: 10.26% (4 / 39)      Branches: 0% (0 / 19)      Functions: 0% (0 / 4)      Lines: 10.26% (4 / 39)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80    1   1   1                                                                                                                                               1  
'use strict';
 
var qs = require('querystring');
 
var pagination = {};
 
pagination.create = function (currentPage, pageCount, queryObj) {
	if (pageCount <= 1) {
		return {
			prev: {page: 1, active: currentPage > 1},
			next: {page: 1, active: currentPage < pageCount},
			rel: [],
			pages: [],
			currentPage: 1,
			pageCount: 1
		};
	}
	pageCount = parseInt(pageCount, 10);
	var pagesToShow = [1, 2, pageCount - 1, pageCount];
 
	currentPage = parseInt(currentPage, 10) || 1;
	var previous = Math.max(1, currentPage - 1);
	var next = Math.min(pageCount, currentPage + 1);
 
	var startPage = Math.max(1, currentPage - 2);
	if (startPage > pageCount - 5) {
		startPage -= 2 - (pageCount - currentPage);
	}
	for(var i = 0; i < 5; ++i) {
		pagesToShow.push(startPage + i);
	}
 
	pagesToShow = pagesToShow.filter(function (page, index, array) {
		return page > 0 && page <= pageCount && array.indexOf(page) === index;
	}).sort(function (a, b) {
		return a - b;
	});
 
	queryObj = queryObj || {};
 
	delete queryObj._;
 
	var pages = pagesToShow.map(function (page) {
		queryObj.page = page;
		return {page: page, active: page === currentPage, qs: qs.stringify(queryObj)};
	});
 
	for (i = pages.length - 1; i > 0; --i) {
		if (pages[i].page - 2 === pages[i - 1].page) {
			pages.splice(i, 0, {page: pages[i].page - 1, active: false, qs: qs.stringify(queryObj)});
		} else if (pages[i].page - 1 !== pages[i - 1].page) {
			pages.splice(i, 0, {separator: true});
		}
	}
 
	var data = {rel: [], pages: pages, currentPage: currentPage, pageCount: pageCount};
	queryObj.page = previous;
	data.prev = {page: previous, active: currentPage > 1, qs: qs.stringify(queryObj)};
	queryObj.page = next;
	data.next = {page: next, active: currentPage < pageCount, qs: qs.stringify(queryObj)};
 
	if (currentPage < pageCount) {
		data.rel.push({
			rel: 'next',
			href: '?page=' + next
		});
	}
 
	if (currentPage > 1) {
		data.rel.push({
			rel: 'prev',
			href: '?page=' + previous
		});
	}
	return data;
};
 
 
module.exports = pagination;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/password.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/password.js

Statements: 35.29% (6 / 17)      Branches: 0% (0 / 6)      Functions: 20% (1 / 5)      Lines: 35.29% (6 / 17)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34    1 1   1       1       1                                   1    
'use strict';
 
(function (module) {
	var fork = require('child_process').fork;
 
	module.hash = function (rounds, password, callback) {
		forkChild({type: 'hash', rounds: rounds, password: password}, callback);
	};
 
	module.compare = function (password, hash, callback) {
		forkChild({type: 'compare', password: password, hash: hash}, callback);
	};
 
	function forkChild(message, callback) {
		var forkProcessParams = {};
		if(global.v8debug || parseInt(process.execArgv.indexOf('--debug'), 10) !== -1) {
			forkProcessParams = {execArgv: ['--debug=' + (5859), '--nolazy']};
		}
		var child = fork('./bcrypt', [], forkProcessParams);
 
		child.on('message', function (msg) {
			if (msg.err) {
				return callback(new Error(msg.err));
			}
 
			callback(null, msg.result);
		});
 
		child.send(message);
	}
 
	return module;
}(exports));
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins.js

Statements: 3.6% (8 / 222)      Branches: 0% (0 / 104)      Functions: 0% (0 / 53)      Lines: 3.6% (8 / 222)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395    60 60 60 60 60 60 60   60                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
'use strict';
 
var fs = require('fs');
var path = require('path');
var async = require('async');
var winston = require('winston');
var semver = require('semver');
var express = require('express');
var nconf = require('nconf');
 
var db = require('./database');
var utils = require('../public/src/utils');
var hotswap = require('./hotswap');
var file = require('./file');
var languages = require('./languages');
 
var app;
var middleware;
 
(function (Plugins) {
	require('./plugins/install')(Plugins);
	require('./plugins/load')(Plugins);
	require('./plugins/hooks')(Plugins);
 
	Plugins.libraries = {};
	Plugins.loadedHooks = {};
	Plugins.staticDirs = {};
	Plugins.cssFiles = [];
	Plugins.lessFiles = [];
	Plugins.clientScripts = [];
	Plugins.acpScripts = [];
	Plugins.customLanguages = {};
	Plugins.customLanguageFallbacks = {};
	Plugins.libraryPaths = [];
	Plugins.versionWarning = [];
	Plugins.languageCodes = [];
 
	Plugins.initialized = false;
 
	Plugins.requireLibrary = function (pluginID, libraryPath) {
		Plugins.libraries[pluginID] = require(libraryPath);
		Plugins.libraryPaths.push(libraryPath);
	};
 
	Plugins.init = function (nbbApp, nbbMiddleware, callback) {
		callback = callback || function () {};
		if (Plugins.initialized) {
			return callback();
		}
 
		if (nbbApp) {
			app = nbbApp;
			middleware = nbbMiddleware;
			hotswap.prepare(nbbApp);
		}
 
		if (global.env === 'development') {
			winston.verbose('[plugins] Initializing plugins system');
		}
 
		Plugins.reload(function (err) {
			if (err) {
				winston.error('[plugins] NodeBB encountered a problem while loading plugins', err.message);
				return callback(err);
			}
 
			if (global.env === 'development') {
				winston.info('[plugins] Plugins OK');
			}
 
			Plugins.initialized = true;
			callback();
		});
	};
 
	Plugins.reload = function (callback) {
		// Resetting all local plugin data
		Plugins.libraries = {};
		Plugins.loadedHooks = {};
		Plugins.staticDirs = {};
		Plugins.versionWarning = [];
		Plugins.cssFiles.length = 0;
		Plugins.lessFiles.length = 0;
		Plugins.clientScripts.length = 0;
		Plugins.acpScripts.length = 0;
		Plugins.libraryPaths.length = 0;
 
		async.waterfall([
			function (next) {
				// Build language code list
				languages.list(function (err, languages) {
					if (err) {
						return next(err);
					}
 
					Plugins.languageCodes = languages.map(function (data) {
						return data.code;
					});
 
					next();
				});
			},
			async.apply(Plugins.getPluginPaths),
			function (paths, next) {
				async.eachSeries(paths, Plugins.loadPlugin, next);
			},
			function (next) {
				// If some plugins are incompatible, throw the warning here
				if (Plugins.versionWarning.length && nconf.get('isPrimary') === 'true') {
					process.stdout.write('\n');
					winston.warn('[plugins/load] The following plugins may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing. In the event of an unresponsive NodeBB caused by this plugin, run `./nodebb reset -p PLUGINNAME` to disable it.');
					for(var x = 0,numPlugins = Plugins.versionWarning.length; x < numPlugins; x++) {
						process.stdout.write('  * '.yellow + Plugins.versionWarning[x] + '\n');
					}
					process.stdout.write('\n');
				}
 
				Object.keys(Plugins.loadedHooks).forEach(function (hook) {
					var hooks = Plugins.loadedHooks[hook];
					hooks = hooks.sort(function (a, b) {
						return a.priority - b.priority;
					});
				});
 
				next();
			}
		], callback);
	};
 
	Plugins.reloadRoutes = function (callback) {
		callback = callback || function () {};
		var router = express.Router();
 
		router.hotswapId = 'plugins';
		router.render = function () {
			app.render.apply(app, arguments);
		};
 
		var controllers = require('./controllers');
		Plugins.fireHook('static:app.load', {app: app, router: router, middleware: middleware, controllers: controllers}, function (err) {
			if (err) {
				return winston.error('[plugins] Encountered error while executing post-router plugins hooks: ' + err.message);
			}
 
			hotswap.replace('plugins', router);
			winston.verbose('[plugins] All plugins reloaded and rerouted');
			callback();
		});
	};
 
	Plugins.getTemplates = function (callback) {
		var templates = {},
			tplName;
 
		async.waterfall([
			async.apply(db.getSortedSetRange, 'plugins:active', 0, -1),
			function (plugins, next) {
				var pluginBasePath = path.join(__dirname, '../node_modules');
				var paths = plugins.map(function (plugin) {
					return path.join(pluginBasePath, plugin);
				});
 
				// Filter out plugins with invalid paths
				async.filter(paths, file.exists, function (paths) {
					next(null, paths);
				});
			},
			function (paths, next) {
				async.map(paths, Plugins.loadPluginInfo, next);
			}
		], function (err, plugins) {
			if (err) {
				return callback(err);
			}
 
			async.eachSeries(plugins, function (plugin, next) {
				if (plugin.templates || plugin.id.startsWith('nodebb-theme-')) {
					winston.verbose('[plugins] Loading templates (' + plugin.id + ')');
					var templatesPath = path.join(__dirname, '../node_modules', plugin.id, plugin.templates || 'templates');
					utils.walk(templatesPath, function (err, pluginTemplates) {
						if (pluginTemplates) {
							pluginTemplates.forEach(function (pluginTemplate) {
								if (pluginTemplate.endsWith('.tpl')) {
									tplName = "/" + pluginTemplate.replace(templatesPath, '').substring(1);
 
									if (templates.hasOwnProperty(tplName)) {
										winston.verbose('[plugins] ' + tplName + ' replaced by ' + plugin.id);
									}
 
									templates[tplName] = pluginTemplate;
								} else {
									winston.warn('[plugins] Skipping ' + pluginTemplate + ' by plugin ' + plugin.id);
								}
							});
						} else {
							if (err) {
								winston.error(err);
							} else {
								winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.');
							}
						}
 
						next(false);
					});
				} else {
					next(false);
				}
			}, function (err) {
				callback(err, templates);
			});
		});
	};
 
	Plugins.get = function (id, callback) {
		var url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins/' + id;
 
		require('request')(url, {
			json: true
		}, function (err, res, body) {
			if (res.statusCode === 404 || !body.payload) {
				return callback(err, {});
			}
 
			Plugins.normalise([body.payload], function (err, normalised) {
				normalised = normalised.filter(function (plugin) {
					return plugin.id === id;
				});
				return callback(err, !err ? normalised[0] : undefined);
			});
		});
	};
 
	Plugins.list = function (matching, callback) {
		if (arguments.length === 1 && typeof matching === 'function') {
			callback = matching;
			matching = true;
		}
		var version = require(path.join(nconf.get('base_dir'), 'package.json')).version;
		var url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins' + (matching !== false ? '?version=' + version : '');
 
		require('request')(url, {
			json: true
		}, function (err, res, body) {
			if (err) {
				winston.error('Error parsing plugins : ' + err.message);
				return callback(err);
			}
 
			Plugins.normalise(body, callback);
		});
	};
 
	Plugins.normalise = function (apiReturn, callback) {
		var pluginMap = {};
		var dependencies = require(path.join(nconf.get('base_dir'), 'package.json')).dependencies;
		apiReturn = apiReturn || [];
		for(var i = 0; i < apiReturn.length; ++i) {
			apiReturn[i].id = apiReturn[i].name;
			apiReturn[i].installed = false;
			apiReturn[i].active = false;
			apiReturn[i].url = apiReturn[i].url ? apiReturn[i].url : apiReturn[i].repository ? apiReturn[i].repository.url : '';
			apiReturn[i].latest = apiReturn[i].latest;
			pluginMap[apiReturn[i].name] = apiReturn[i];
		}
 
		Plugins.showInstalled(function (err, installedPlugins) {
			if (err) {
				return callback(err);
			}
 
			installedPlugins = installedPlugins.filter(function (plugin) {
				return plugin && !plugin.system;
			});
 
			async.each(installedPlugins, function (plugin, next) {
				// If it errored out because a package.json or plugin.json couldn't be read, no need to do this stuff
				if (plugin.error) {
					pluginMap[plugin.id] = pluginMap[plugin.id] || {};
					pluginMap[plugin.id].installed = true;
					pluginMap[plugin.id].error = true;
					return next();
				}
 
				pluginMap[plugin.id] = pluginMap[plugin.id] || {};
				pluginMap[plugin.id].id = pluginMap[plugin.id].id || plugin.id;
				pluginMap[plugin.id].name = plugin.name || pluginMap[plugin.id].name;
				pluginMap[plugin.id].description = plugin.description;
				pluginMap[plugin.id].url = pluginMap[plugin.id].url || plugin.url;
				pluginMap[plugin.id].installed = true;
				pluginMap[plugin.id].isTheme = !!plugin.id.match('nodebb-theme-');
				pluginMap[plugin.id].error = plugin.error || false;
				pluginMap[plugin.id].active = plugin.active;
				pluginMap[plugin.id].version = plugin.version;
 
				// If package.json defines a version to use, stick to that
				if (dependencies.hasOwnProperty(plugin.id) && semver.valid(dependencies[plugin.id])) {
					pluginMap[plugin.id].latest = dependencies[plugin.id];
				} else {
					pluginMap[plugin.id].latest = pluginMap[plugin.id].latest || plugin.version;
				}
				pluginMap[plugin.id].outdated = semver.gt(pluginMap[plugin.id].latest, pluginMap[plugin.id].version);
				next();
			}, function (err) {
				if (err) {
					return callback(err);
				}
 
				var pluginArray = [];
 
				for (var key in pluginMap) {
					if (pluginMap.hasOwnProperty(key)) {
						pluginArray.push(pluginMap[key]);
					}
				}
 
				pluginArray.sort(function (a, b) {
					if (a.name > b.name ) {
						return 1;
					} else if (a.name < b.name ) {
						return -1;
					} else {
						return 0;
					}
				});
 
				callback(null, pluginArray);
			});
		});
	};
 
	Plugins.showInstalled = function (callback) {
		var npmPluginPath = path.join(__dirname, '../node_modules');
 
		async.waterfall([
			async.apply(fs.readdir, npmPluginPath),
 
			function (dirs, next) {
				dirs = dirs.filter(function (dir) {
					return dir.startsWith('nodebb-plugin-') ||
						dir.startsWith('nodebb-widget-') ||
						dir.startsWith('nodebb-rewards-') ||
						dir.startsWith('nodebb-theme-');
				}).map(function (dir) {
					return path.join(npmPluginPath, dir);
				});
 
				async.filter(dirs, function (dir, callback) {
					fs.stat(dir, function (err, stats) {
						callback(!err && stats.isDirectory());
					});
				}, function (plugins) {
					next(null, plugins);
				});
			},
 
			function (files, next) {
				var plugins = [];
 
				async.each(files, function (file, next) {
					async.waterfall([
						function (next) {
							Plugins.loadPluginInfo(file, next);
						},
						function (pluginData, next) {
							Plugins.isActive(pluginData.name, function (err, active) {
								if (err) {
									return next(new Error('no-active-state'));
								}
 
								delete pluginData.hooks;
								delete pluginData.library;
								pluginData.active = active;
								pluginData.installed = true;
								pluginData.error = false;
								next(null, pluginData);
							});
						}
					], function (err, pluginData) {
						if (err) {
							return next(); // Silently fail
						}
 
						plugins.push(pluginData);
						next();
					});
				}, function (err) {
					next(err, plugins);
				});
			}
		], callback);
	};
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts.js

Statements: 2.04% (3 / 147)      Branches: 0% (0 / 84)      Functions: 0% (0 / 44)      Lines: 2.04% (3 / 147)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275    18 18   18                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
'use strict';
 
var async = require('async');
var _ = require('underscore');
 
var db = require('./database');
var utils = require('../public/src/utils');
var user = require('./user');
var topics = require('./topics');
var privileges = require('./privileges');
var plugins = require('./plugins');
 
(function (Posts) {
 
	require('./posts/create')(Posts);
	require('./posts/delete')(Posts);
	require('./posts/edit')(Posts);
	require('./posts/parse')(Posts);
	require('./posts/user')(Posts);
	require('./posts/topics')(Posts);
	require('./posts/category')(Posts);
	require('./posts/summary')(Posts);
	require('./posts/recent')(Posts);
	require('./posts/flags')(Posts);
	require('./posts/tools')(Posts);
	require('./posts/votes')(Posts);
	require('./posts/bookmarks')(Posts);
 
	Posts.exists = function (pid, callback) {
		db.isSortedSetMember('posts:pid', pid, callback);
	};
 
	Posts.getPidsFromSet = function (set, start, stop, reverse, callback) {
		if (isNaN(start) || isNaN(stop)) {
			return callback(null, []);
		}
		db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop, callback);
	};
 
	Posts.getPostsByPids = function (pids, uid, callback) {
		if (!Array.isArray(pids) || !pids.length) {
			return callback(null, []);
		}
 
		var keys = [];
 
		for (var x = 0, numPids = pids.length; x < numPids; ++x) {
			keys.push('post:' + pids[x]);
		}
 
		async.waterfall([
			function (next) {
				db.getObjects(keys, next);
			},
			function (posts, next) {
				async.map(posts, function (post, next) {
					if (!post) {
						return next();
					}
					post.upvotes = parseInt(post.upvotes, 10) || 0;
					post.downvotes = parseInt(post.downvotes, 10) || 0;
					post.votes = post.upvotes - post.downvotes;
					post.timestampISO = utils.toISOString(post.timestamp);
					post.editedISO = parseInt(post.edited, 10) !== 0 ? utils.toISOString(post.edited) : '';
					Posts.parsePost(post, next);
				}, next);
			},
			function (posts, next) {
				plugins.fireHook('filter:post.getPosts', {posts: posts, uid: uid}, next);
			},
			function (data, next) {
				if (!data || !Array.isArray(data.posts)) {
					return next(null, []);
				}
				data.posts = data.posts.filter(Boolean);
				next(null, data.posts);
			}
		], callback);
	};
 
	Posts.getPostSummariesFromSet = function (set, uid, start, stop, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange(set, start, stop, next);
			},
			function (pids, next) {
				privileges.posts.filter('read', pids, uid, next);
			},
			function (pids, next) {
				Posts.getPostSummaryByPids(pids, uid, {stripTags: false}, next);
			},
			function (posts, next) {
				next(null, {posts: posts, nextStart: stop + 1});
			}
		], callback);
	};
 
	Posts.getPostData = function (pid, callback) {
		db.getObject('post:' + pid, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			plugins.fireHook('filter:post.get', data, callback);
		});
	};
 
	Posts.getPostField = function (pid, field, callback) {
		Posts.getPostFields(pid, [field], function (err, data) {
			if (err) {
				return callback(err);
			}
 
			callback(null, data[field]);
		});
	};
 
	Posts.getPostFields = function (pid, fields, callback) {
		db.getObjectFields('post:' + pid, fields, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			data.pid = pid;
 
			plugins.fireHook('filter:post.getFields', {posts: [data], fields: fields}, function (err, data) {
				callback(err, (data && Array.isArray(data.posts) && data.posts.length) ? data.posts[0] : null);
			});
		});
	};
 
	Posts.getPostsFields = function (pids, fields, callback) {
		if (!Array.isArray(pids) || !pids.length) {
			return callback(null, []);
		}
 
		var keys = pids.map(function (pid) {
			return 'post:' + pid;
		});
 
		db.getObjectsFields(keys, fields, function (err, posts) {
			if (err) {
				return callback(err);
			}
			plugins.fireHook('filter:post.getFields', {posts: posts, fields: fields}, function (err, data) {
				callback(err, (data && Array.isArray(data.posts)) ? data.posts : null);
			});
		});
	};
 
	Posts.setPostField = function (pid, field, value, callback) {
		db.setObjectField('post:' + pid, field, value, function (err) {
			if (err) {
				return callback(err);
			}
			var data = {
				pid: pid
			};
			data[field] = value;
			plugins.fireHook('action:post.setFields', data);
			callback();
		});
	};
 
	Posts.setPostFields = function (pid, data, callback) {
		db.setObject('post:' + pid, data, function (err) {
			if (err) {
				return callback(err);
			}
			data.pid = pid;
			plugins.fireHook('action:post.setFields', data);
			callback();
		});
	};
 
	Posts.getPidIndex = function (pid, tid, topicPostSort, callback) {
		var set = topicPostSort === 'most_votes' ? 'tid:' + tid + ':posts:votes' : 'tid:' + tid + ':posts';
		db.sortedSetRank(set, pid, function (err, index) {
			if (!utils.isNumber(index)) {
				return callback(err, 0);
			}
			callback(err, parseInt(index, 10) + 1);
		});
	};
 
	Posts.getPostIndices = function (posts, uid, callback) {
		if (!Array.isArray(posts) || !posts.length) {
			return callback(null, []);
		}
 
		user.getSettings(uid, function (err, settings) {
			if (err) {
				return callback(err);
			}
 
			var byVotes = settings.topicPostSort === 'most_votes';
			var sets = posts.map(function (post) {
				return byVotes ? 'tid:' + post.tid + ':posts:votes' : 'tid:' + post.tid + ':posts';
			});
 
			var uniqueSets = _.uniq(sets);
			var method = 'sortedSetsRanks';
			if (uniqueSets.length === 1) {
				method = 'sortedSetRanks';
				sets = uniqueSets[0];
			}
 
			var pids = posts.map(function (post) {
				return post.pid;
			});
 
			db[method](sets, pids, function (err, indices) {
				if (err) {
					return callback(err);
				}
 
				for (var i = 0; i < indices.length; ++i) {
					indices[i] = utils.isNumber(indices[i]) ? parseInt(indices[i], 10) + 1 : 0;
				}
 
				callback(null, indices);
			});
		});
	};
 
	Posts.updatePostVoteCount = function (postData, callback) {
		if (!postData || !postData.pid || !postData.tid) {
			return callback();
		}
		async.parallel([
			function (next) {
				if (postData.uid) {
					if (postData.votes > 0) {
						db.sortedSetAdd('uid:' + postData.uid + ':posts:votes', postData.votes, postData.pid, next);
					} else {
						db.sortedSetRemove('uid:' + postData.uid + ':posts:votes', postData.pid, next);
					}
				} else {
					next();
				}
			},
			function (next) {
				async.waterfall([
					function (next) {
						topics.getTopicField(postData.tid, 'mainPid', next);
					},
					function (mainPid, next) {
						if (parseInt(mainPid, 10) === parseInt(postData.pid, 10)) {
							return next();
						}
						db.sortedSetAdd('tid:' + postData.tid + ':posts:votes', postData.votes, postData.pid, next);
					}
				], next);
			},
			function (next) {
				Posts.setPostFields(postData.pid, {upvotes: postData.upvotes, downvotes: postData.downvotes}, next);
			}
		], function (err) {
			callback(err);
		});
	};
 
	Posts.modifyPostByPrivilege = function (post, isAdminOrMod) {
		if (post.deleted && !(isAdminOrMod || post.selfPost)) {
			post.content = '[[topic:post_is_deleted]]';
			if (post.user) {
				post.user.signature = '';
			}
		}
	};
 
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges.js

Statements: 62.5% (5 / 8)      Branches: 100% (0 / 0)      Functions: 100% (0 / 0)      Lines: 62.5% (5 / 8)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42    8   8                             8                             8   8          
"use strict";
 
var privileges = module.exports;
 
privileges.userPrivilegeList = [
	'find',
	'read',
	'topics:read',
	'topics:create',
	'topics:reply',
	'posts:edit',
	'posts:delete',
	'topics:delete',
	'upload:post:image',
	'upload:post:file',
	'purge',
	'mods'
];
 
privileges.groupPrivilegeList = [
	'groups:find',
	'groups:read',
	'groups:topics:read',
	'groups:topics:create',
	'groups:topics:reply',
	'groups:posts:edit',
	'groups:posts:delete',
	'groups:topics:delete',
	'groups:upload:post:image',
	'groups:upload:post:file',
	'groups:purge',
	'groups:moderate'
];
 
privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList);
 
require('./privileges/categories')(privileges);
require('./privileges/topics')(privileges);
require('./privileges/posts')(privileges);
require('./privileges/users')(privileges);
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/pubsub.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/pubsub.js

Statements: 37.5% (9 / 24)      Branches: 16.67% (1 / 6)      Functions: 33.33% (1 / 3)      Lines: 37.5% (9 / 24)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49      1         1   1 1 1                                             1   1               1   1  
 
'use strict';
 
var nconf = require('nconf'),
	util = require('util'),
	winston = require('winston'),
	EventEmitter = require('events').EventEmitter;
 
var channelName;
 
var PubSub = function () {
	var self = this;
	Iif (nconf.get('redis')) {
		var redis = require('./database/redis');
		var subClient = redis.connect();
		this.pubClient = redis.connect();
 
		channelName = 'db:' + nconf.get('redis:database') + 'pubsub_channel';
		subClient.subscribe(channelName);
 
		subClient.on('message', function (channel, message) {
			if (channel !== channelName) {
				return;
			}
 
			try {
				var msg = JSON.parse(message);
				self.emit(msg.event, msg.data);
			} catch(err) {
				winston.error(err.stack);
			}
		});
	}
};
 
util.inherits(PubSub, EventEmitter);
 
PubSub.prototype.publish = function (event, data) {
	if (this.pubClient) {
		this.pubClient.publish(channelName, JSON.stringify({event: event, data: data}));
	} else {
		this.emit(event, data);
	}
};
 
var pubsub = new PubSub();
 
module.exports = pubsub;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/reset.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/reset.js

Statements: 10.87% (10 / 92)      Branches: 0% (0 / 36)      Functions: 0% (0 / 17)      Lines: 10.87% (10 / 92)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166    2 2 2 2                                                                                                           1                       1                                                 1                               1                                                           1                     1                        
'use strict';
 
var winston = require('winston');
var nconf = require('nconf');
var async = require('async');
var db = require('./database');
 
var Reset = {};
 
 
Reset.reset = function () {
	db.init(function (err) {
		if (err) {
			winston.error(err.message);
			process.exit();
		}
 
		if (nconf.get('t')) {
			if(nconf.get('t') === true) {
				resetThemes();
			} else {
				resetTheme(nconf.get('t'));
			}
		} else if (nconf.get('p')) {
			if (nconf.get('p') === true) {
				resetPlugins();
			} else {
				resetPlugin(nconf.get('p'));
			}
		} else if (nconf.get('w')) {
			resetWidgets();
		} else if (nconf.get('s')) {
			resetSettings();
		} else if (nconf.get('a')) {
			require('async').series([resetWidgets, resetThemes, resetPlugins, resetSettings], function (err) {
				if (!err) {
					winston.info('[reset] Reset complete.');
				} else {
					winston.error('[reset] Errors were encountered while resetting your forum settings: %s', err.message);
				}
				process.exit();
			});
		} else {
			process.stdout.write('\nNodeBB Reset\n'.bold);
			process.stdout.write('No arguments passed in, so nothing was reset.\n\n'.yellow);
			process.stdout.write('Use ./nodebb reset ' + '{-t|-p|-w|-s|-a}\n'.red);
			process.stdout.write('    -t\tthemes\n');
			process.stdout.write('    -p\tplugins\n');
			process.stdout.write('    -w\twidgets\n');
			process.stdout.write('    -s\tsettings\n');
			process.stdout.write('    -a\tall of the above\n');
 
			process.stdout.write('\nPlugin and theme reset flags (-p & -t) can take a single argument\n');
			process.stdout.write('    e.g. ./nodebb reset -p nodebb-plugin-mentions, ./nodebb reset -t nodebb-theme-persona\n');
			process.exit();
		}
	});
};
 
function resetSettings(callback) {
	var meta = require('./meta');
	meta.configs.set('allowLocalLogin', 1, function (err) {
		winston.info('[reset] Settings reset to default');
		if (typeof callback === 'function') {
			callback(err);
		} else {
			process.exit();
		}
	});
}
 
function resetTheme(themeId) {
	var meta = require('./meta');
	var fs = require('fs');
	
	fs.access('node_modules/' + themeId + '/package.json', function (err, fd) {
		if (err) {
			winston.warn('[reset] Theme `%s` is not installed on this forum', themeId);
			process.exit();
		} else {
			meta.themes.set({
				type: 'local',
				id: themeId
			}, function (err) {
				if (err) {
					winston.warn('[reset] Failed to reset theme to ' + themeId);
				} else {
					winston.info('[reset] Theme reset to ' + themeId);
				}
 
				process.exit();
			});		
		}
	});
}
 
function resetThemes(callback) {
	var meta = require('./meta');
 
	meta.themes.set({
		type: 'local',
		id: 'nodebb-theme-persona'
	}, function (err) {
		winston.info('[reset] Theme reset to Persona');
		if (typeof callback === 'function') {
			callback(err);
		} else {
			process.exit();
		}
	});
}
 
function resetPlugin(pluginId) {
	var active = false;
 
	async.waterfall([
		async.apply(db.isSortedSetMember, 'plugins:active', pluginId),
		function (isMember, next) {
			active = isMember;
 
			if (isMember) {
				db.sortedSetRemove('plugins:active', pluginId, next);
			} else {
				next();
			}
		}
	], function (err) {
		if (err) {
			winston.error('[reset] Could not disable plugin: %s encountered error %s', pluginId, err.message);
		} else {
			if (active) {
				winston.info('[reset] Plugin `%s` disabled', pluginId);
			} else {
				winston.warn('[reset] Plugin `%s` was not active on this forum', pluginId);
				winston.info('[reset] No action taken.');
			}
		}
 
		process.exit();
	});
}
 
function resetPlugins(callback) {
	db.delete('plugins:active', function (err) {
		winston.info('[reset] All Plugins De-activated');
		if (typeof callback === 'function') {
			callback(err);
		} else {
			process.exit();
		}
	});
}
 
function resetWidgets(callback) {
	require('./widgets').reset(function (err) {
		winston.info('[reset] All Widgets moved to Draft Zone');
		if (typeof callback === 'function') {
			callback(err);
		} else {
			process.exit();
		}
	});
}
 
module.exports = Reset;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/search.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/search.js

Statements: 5.53% (12 / 217)      Branches: 0% (0 / 153)      Functions: 0% (0 / 63)      Lines: 5.53% (12 / 217)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424    2 2   2                                                                           1                                                                                                                                               1                                               1                                                                                                                                                                                                                                                             1                               1                                 1                                                                                 1                                                                                         1                                       1                                    
'use strict';
 
var async = require('async');
var validator = require('validator');
 
var db = require('./database');
var posts = require('./posts');
var topics = require('./topics');
var categories = require('./categories');
var user = require('./user');
var plugins = require('./plugins');
var privileges = require('./privileges');
var utils = require('../public/src/utils');
 
var search = {};
 
module.exports = search;
 
search.search = function (data, callback) {
 
	var start = process.hrtime();
	var searchIn = data.searchIn || 'titlesposts';
 
	async.waterfall([
		function (next) {
			if (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts') {
				searchInContent(data, next);
			} else if (searchIn === 'users') {
				user.search(data, next);
			} else if (searchIn === 'tags') {
				topics.searchAndLoadTags(data, next);
			} else {
				next(new Error('[[error:unknown-search-filter]]'));
			}
		},
		function (result, next) {
			result.search_query = validator.escape(String(data.query || ''));
			result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2);
			next(null, result);
		}
	], callback);
};
 
function searchInContent(data, callback) {
	data.uid = data.uid || 0;
	async.parallel({
		searchCids: function (next) {
			getSearchCids(data, next);
		},
		searchUids: function (next) {
			getSearchUids(data, next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		async.parallel({
			pids: function (next) {
				if (data.searchIn === 'posts' || data.searchIn === 'titlesposts') {
					search.searchQuery('post', data.query, results.searchCids, results.searchUids, next);
				} else {
					next(null, []);
				}
			},
			tids: function (next) {
				if (data.searchIn === 'titles' || data.searchIn === 'titlesposts') {
					search.searchQuery('topic', data.query, results.searchCids, results.searchUids, next);
				} else {
					next(null, []);
				}
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var matchCount = 0;
			if (!results || (!results.pids.length && !results.tids.length)) {
				return callback(null, {posts: [], matchCount: matchCount, pageCount: 1});
			}
 
			async.waterfall([
				function (next) {
					topics.getMainPids(results.tids, next);
				},
				function (mainPids, next) {
					results.pids = mainPids.concat(results.pids).map(function (pid) {
						return pid && pid.toString();
					}).filter(function (pid, index, array) {
						return pid && array.indexOf(pid) === index;
					});
 
					privileges.posts.filter('read', results.pids, data.uid, next);
				},
				function (pids, next) {
					filterAndSort(pids, data, next);
				},
				function (pids, next) {
					matchCount = pids.length;
					if (data.page) {
						var start = Math.max(0, (data.page - 1)) * 10;
						pids = pids.slice(start, start + 10);
					}
 
					posts.getPostSummaryByPids(pids, data.uid, {}, next);
				},
				function (posts, next) {
					next(null, {posts: posts, matchCount: matchCount, pageCount: Math.max(1, Math.ceil(parseInt(matchCount, 10) / 10))});
				}
			], callback);
		});
	});
}
 
function filterAndSort(pids, data, callback) {
	getMatchedPosts(pids, data, function (err, posts) {
		if (err) {
			return callback(err);
		}
 
		if (!Array.isArray(posts) || !posts.length) {
			return callback(null, pids);
		}
		posts = posts.filter(Boolean);
 
		posts = filterByPostcount(posts, data.replies, data.repliesFilter);
		posts = filterByTimerange(posts, data.timeRange, data.timeFilter);
 
		sortPosts(posts, data);
 
		pids = posts.map(function (post) {
			return post && post.pid;
		});
 
		callback(null, pids);
	});
}
 
function getMatchedPosts(pids, data, callback) {
	var postFields = ['pid', 'tid', 'timestamp', 'deleted'];
	var topicFields = ['deleted'];
	var categoryFields = [];
 
	if (data.replies) {
		topicFields.push('postcount');
	}
 
	if (data.sortBy) {
		if (data.sortBy.startsWith('category')) {
			topicFields.push('cid');
		} else if (data.sortBy.startsWith('topic.')) {
			topicFields.push(data.sortBy.split('.')[1]);
		} else if (data.sortBy.startsWith('user.')) {
			postFields.push('uid');
		} else if (data.sortBy.startsWith('category.')) {
			categoryFields.push(data.sortBy.split('.')[1]);
		} else if (data.sortBy.startsWith('teaser')) {
			topicFields.push('teaserPid');
		}
	}
 
	var posts;
	async.waterfall([
		function (next) {
			var keys = pids.map(function (pid) {
				return 'post:' + pid;
			});
			db.getObjectsFields(keys, postFields, next);
		},
		function (_posts, next) {
			posts = _posts.filter(function (post) {
				return post && parseInt(post.deleted, 10) !== 1;
			});
 
			async.parallel({
				users: function (next) {
					if (data.sortBy && data.sortBy.startsWith('user')) {
						var uids = posts.map(function (post) {
							return post.uid;
						});
						user.getUsersFields(uids, ['username'], next);
					} else {
						next();
					}
				},
				topics: function (next) {
					var topics;
					async.waterfall([
						function (next) {
							var topicKeys = posts.map(function (post) {
								return 'topic:' + post.tid;
							});
							db.getObjectsFields(topicKeys, topicFields, next);
						},
						function (_topics, next) {
							topics = _topics;
 
							async.parallel({
								teasers: function (next) {
									if (topicFields.indexOf('teaserPid') !== -1) {
										var teaserKeys = topics.map(function (topic) {
											return 'post:' + topic.teaserPid;
										});
										db.getObjectsFields(teaserKeys, ['timestamp'], next);
									} else {
										next();
									}
								},
								categories: function (next) {
									if (!categoryFields.length) {
										return next();
									}
									var cids = topics.map(function (topic) {
										return 'category:' + topic.cid;
									});
									db.getObjectsFields(cids, categoryFields, next);
								}
							}, next);
						}
					], function (err, results) {
						if (err) {
							return next(err);
						}
 
						topics.forEach(function (topic, index) {
							if (topic && results.categories && results.categories[index]) {
								topic.category = results.categories[index];
							}
							if (topic && results.teasers && results.teasers[index]) {
								topic.teaser = results.teasers[index];
							}
						});
 
						next(null, topics);
					});
				}
			}, next);
		},
		function (results, next) {
 
			posts.forEach(function (post, index) {
				if (results.topics && results.topics[index]) {
					post.topic = results.topics[index];
					if (results.topics[index].category) {
						post.category = results.topics[index].category;
					}
					if (results.topics[index].teaser) {
						post.teaser = results.topics[index].teaser;
					}
				}
 
				if (results.users && results.users[index]) {
					post.user = results.users[index];
				}
			});
 
			posts = posts.filter(function (post) {
				return post && post.topic && parseInt(post.topic.deleted, 10) !== 1;
			});
 
			next(null, posts);
		}
	], callback);
}
 
function filterByPostcount(posts, postCount, repliesFilter) {
	postCount = parseInt(postCount, 10);
	if (postCount) {
		if (repliesFilter === 'atleast') {
			posts = posts.filter(function (post) {
				return post.topic && post.topic.postcount >= postCount;
			});
		} else {
			posts = posts.filter(function (post) {
				return post.topic && post.topic.postcount <= postCount;
			});
		}
	}
	return posts;
}
 
function filterByTimerange(posts, timeRange, timeFilter) {
	timeRange = parseInt(timeRange) * 1000;
	if (timeRange) {
		var time = Date.now() - timeRange;
		if (timeFilter === 'newer') {
			posts = posts.filter(function (post) {
				return post.timestamp >= time;
			});
		} else {
			posts = posts.filter(function (post) {
				return post.timestamp <= time;
			});
		}
	}
	return posts;
}
 
function sortPosts(posts, data) {
	if (!posts.length || !data.sortBy) {
		return;
	}
 
	data.sortDirection = data.sortDirection || 'desc';
	var direction = data.sortDirection === 'desc' ? 1 : -1;
 
	if (data.sortBy === 'timestamp') {
		posts.sort(function (p1, p2) {
			return direction * (p2.timestamp - p1.timestamp);
		});
 
		return;
	}
 
	var firstPost = posts[0];
	var fields = data.sortBy.split('.');
 
	if (!fields || fields.length !== 2 || !firstPost[fields[0]] || !firstPost[fields[0]][fields[1]]) {
		return;
	}
 
	var isNumeric = utils.isNumber(firstPost[fields[0]][fields[1]]);
 
	if (isNumeric) {
		posts.sort(function (p1, p2) {
			return direction * (p2[fields[0]][fields[1]] - p1[fields[0]][fields[1]]);
		});
	} else {
		posts.sort(function (p1, p2) {
			if (p1[fields[0]][fields[1]] > p2[fields[0]][fields[1]]) {
				return direction;
			} else if (p1[fields[0]][fields[1]] < p2[fields[0]][fields[1]]) {
				return -direction;
			}
			return 0;
		});
	}
}
 
function getSearchCids(data, callback) {
	if (!Array.isArray(data.categories) || !data.categories.length) {
		return callback(null, []);
	}
 
	if (data.categories.indexOf('all') !== -1) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('categories:cid', 0, -1, next);
			},
			function (cids, next) {
				privileges.categories.filterCids('read', cids, data.uid, next);
			}
		], callback);
		return;
	}
 
	async.parallel({
		watchedCids: function (next) {
			if (data.categories.indexOf('watched') !== -1) {
				user.getWatchedCategories(data.uid, next);
			} else {
				next(null, []);
			}
		},
		childrenCids: function (next) {
			if (data.searchChildren) {
				getChildrenCids(data.categories, data.uid, next);
			} else {
				next(null, []);
			}
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		var cids = results.watchedCids.concat(results.childrenCids).concat(data.categories).filter(function (cid, index, array) {
			return cid && array.indexOf(cid) === index;
		});
 
		callback(null, cids);
	});
}
 
function getChildrenCids(cids, uid, callback) {
	categories.getChildren(cids, uid, function (err, childrenCategories) {
		if (err) {
			return callback(err);
		}
 
		var childrenCids = [];
		var allCategories = [];
 
		childrenCategories.forEach(function (childrens) {
			categories.flattenCategories(allCategories, childrens);
		 	childrenCids = childrenCids.concat(allCategories.map(function (category) {
		 		return category && category.cid;
		 	}));
		 });
 
		callback(null, childrenCids);
	});
}
 
function getSearchUids(data, callback) {
	if (data.postedBy) {
		user.getUidsByUsernames(Array.isArray(data.postedBy) ? data.postedBy : [data.postedBy], callback);
	} else {
		callback(null, []);
	}
}
 
search.searchQuery = function (index, content, cids, uids, callback) {
	plugins.fireHook('filter:search.query', {
		index: index,
		content: content,
		cid: cids,
		uid: uids
	}, callback);
};
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/settings.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/settings.js

Statements: 4.46% (5 / 112)      Branches: 0% (0 / 73)      Functions: 0% (0 / 16)      Lines: 4.46% (5 / 112)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235    2   1                                       1                           1                                             1                                                                                                                                                                                                                                                                                                                                                          
"use strict";
 
var meta = require('./meta');
 
function expandObjBy(obj1, obj2) {
	var key, val1, val2, xorValIsArray, changed = false;
	for (key in obj2) {
		if (obj2.hasOwnProperty(key)) {
			val2 = obj2[key];
			val1 = obj1[key];
			xorValIsArray = Array.isArray(val1) ^ Array.isArray(val2);
			if (xorValIsArray || !obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) {
				obj1[key] = val2;
				changed = true;
			} else if (typeof val2 === 'object' && !Array.isArray(val2)) {
				if (expandObjBy(val1, val2)) {
					changed = true;
				}
			}
		}
	}
	return changed;
}
 
function trim(obj1, obj2) {
	var key, val1;
	for (key in obj1) {
		if (obj1.hasOwnProperty(key)) {
			val1 = obj1[key];
			if (!obj2.hasOwnProperty(key)) {
				delete obj1[key];
			} else if (typeof val1 === 'object' && !Array.isArray(val1)) {
				trim(val1, obj2[key]);
			}
		}
	}
}
 
function mergeSettings(cfg, defCfg) {
	if (typeof defCfg !== 'object') {
		return;
	}
	if (typeof cfg._ !== 'object') {
		cfg._ = defCfg;
	} else {
		expandObjBy(cfg._, defCfg);
		trim(cfg._, defCfg);
	}
}
 
/**
 A class to manage Objects saved in {@link meta.settings} within property "_".
 Constructor, synchronizes the settings and repairs them if version differs.
 @param hash The hash to use for {@link meta.settings}.
 @param version The version of the settings, used to determine whether the saved settings may be corrupt.
 @param defCfg The default settings.
 @param callback Gets called once the Settings-object is ready.
 @param forceUpdate Whether to trigger structure-update even if the version doesn't differ from saved one.
 Should be true while plugin-development to ensure structure-changes within settings persist.
 @param reset Whether to reset the settings.
 */
function Settings(hash, version, defCfg, callback, forceUpdate, reset) {
	this.hash = hash;
	this.version = version || this.version;
	this.defCfg = defCfg;
	if (reset) {
		this.reset(callback);
	} else {
		this.sync(function () {
			this.checkStructure(callback, forceUpdate);
		});
	}
}
 
Settings.prototype.hash = '';
Settings.prototype.defCfg = {};
Settings.prototype.cfg = {};
Settings.prototype.version = '0.0.0';
 
/**
 Synchronizes the local object with the saved object (reverts changes).
 @param callback Gets called when done.
 */
Settings.prototype.sync = function (callback) {
	var _this = this;
	meta.settings.get(this.hash, function (err, settings) {
		try {
			if (settings._) {
				settings._ = JSON.parse(settings._);
			}
		} catch (_error) {}
		_this.cfg = settings;
		if (typeof _this.cfg._ !== 'object') {
			_this.cfg._ = _this.defCfg;
			_this.persist(callback);
		} else if (expandObjBy(_this.cfg._, _this.defCfg)) {
			_this.persist(callback);
		} else if (typeof callback === 'function') {
			callback.apply(_this, err);
		}
	});
};
 
/**
 Persists the local object.
 @param callback Gets called when done.
 */
Settings.prototype.persist = function (callback) {
	var conf = this.cfg._,
		_this = this;
	if (typeof conf === 'object') {
		conf = JSON.stringify(conf);
	}
	meta.settings.set(this.hash, this.createWrapper(this.cfg.v, conf), function () {
		if (typeof callback === 'function') {
			callback.apply(_this, arguments || []);
		}
	});
	return this;
};
 
/**
 Returns the setting of given key or default value if not set.
 @param key The key of the setting to return.
 @param def The default value, if not set global default value gets used.
 @returns Object The setting to be used.
 */
Settings.prototype.get = function (key, def) {
	var obj = this.cfg._,
		parts = (key || '').split('.'),
		part;
	for (var i = 0; i < parts.length; i++) {
		part = parts[i];
		if (part && obj != null) {
			obj = obj[part];
		}
	}
	if (obj === void 0) {
		if (def === void 0) {
			def = this.defCfg;
			for (var j = 0; j < parts.length; j++) {
				part = parts[j];
				if (part && def != null) {
					def = def[part];
				}
			}
		}
		return def;
	}
	return obj;
};
 
/**
 Returns the settings-wrapper object.
 @returns Object The settings-wrapper.
 */
Settings.prototype.getWrapper = function () {
	return this.cfg;
};
 
/**
 Creates a new wrapper for the given settings with the given version.
 @returns Object The new settings-wrapper.
 */
Settings.prototype.createWrapper = function (version, settings) {
	return {
		v: version,
		_: settings
	};
};
 
/**
 Creates a new wrapper for the default settings.
 @returns Object The new settings-wrapper.
 */
Settings.prototype.createDefaultWrapper = function () {
	return this.createWrapper(this.version, this.defCfg);
};
 
/**
 Sets the setting of given key to given value.
 @param key The key of the setting to set.
 @param val The value to set.
 */
Settings.prototype.set = function (key, val) {
	var part, obj, parts;
	this.cfg.v = this.version;
	if (val == null || !key) {
		this.cfg._ = val || key;
	} else {
		obj = this.cfg._;
		parts = key.split('.');
		for (var i = 0, _len = parts.length - 1; i < _len; i++) {
			if (part = parts[i]) {
				if (!obj.hasOwnProperty(part)) {
					obj[part] = {};
				}
				obj = obj[part];
			}
		}
		obj[parts[parts.length - 1]] = val;
	}
	return this;
};
 
/**
 Resets the saved settings to default settings.
 @param callback Gets called when done.
 */
Settings.prototype.reset = function (callback) {
	this.set(this.defCfg).persist(callback);
	return this;
};
 
/**
 If the version differs the settings get updated and persisted.
 @param callback Gets called when done.
 @param force Whether to update and persist the settings even if the versions ara equal.
 */
Settings.prototype.checkStructure = function (callback, force) {
	if (!force && this.cfg.v === this.version) {
		if (typeof callback === 'function') {
			callback();
		}
	} else {
		mergeSettings(this.cfg, this.defCfg);
		this.cfg.v = this.version;
		this.persist(callback);
	}
	return this;
};
 
module.exports = Settings;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/sitemap.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/sitemap.js

Statements: 5.88% (4 / 68)      Branches: 0% (0 / 32)      Functions: 0% (0 / 15)      Lines: 5.88% (4 / 68)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185    4 4 4   4                                                                                                                                                                                                                                                                                                                                                                    
'use strict';
 
var async = require('async');
var sm = require('sitemap');
var nconf = require('nconf');
 
var db = require('./database');
var categories = require('./categories');
var topics = require('./topics');
var privileges = require('./privileges');
var meta = require('./meta');
var plugins = require('./plugins');
var utils = require('../public/src/utils');
 
var sitemap = {
	maps: {
		topics: []
	}
};
 
sitemap.render = function (callback) {
	var numTopics = parseInt(meta.config.sitemapTopics, 10) || 500;
	var returnData = {
			url: nconf.get('url'),
			topics: []
		};
	var numPages;
 
	async.waterfall([
		async.apply(db.getSortedSetRange, 'topics:recent', 0, -1),
		function (tids, next) {
			privileges.topics.filterTids('read', tids, 0, next);
		}
	], function (err, tids) {
		if (err) {
			numPages = 1;
		} else {
			numPages = Math.ceil(tids.length / numTopics);
		}
 
		for(var x = 1; x <= numPages; x++) {
			returnData.topics.push(x);
		}
 
		callback(null, returnData);
	});
};
 
sitemap.getPages = function (callback) {
	if (
		sitemap.maps.pages &&
		Date.now() < parseInt(sitemap.maps.pages.cacheSetTimestamp, 10) + parseInt(sitemap.maps.pages.cacheResetPeriod, 10)
	) {
		return sitemap.maps.pages.toXML(callback);
	}
 
	var urls = [{
			url: '',
			changefreq: 'weekly',
			priority: 0.6
		}, {
			url: '/recent',
			changefreq: 'daily',
			priority: 0.4
		}, {
			url: '/users',
			changefreq: 'daily',
			priority: 0.4
		}, {
			url: '/groups',
			changefreq: 'daily',
			priority: 0.4
		}];
 
	plugins.fireHook('filter:sitemap.getPages', {urls: urls}, function (err, data) {
		if (err) {
			return callback(err);
		}
		sitemap.maps.pages = sm.createSitemap({
			hostname: nconf.get('url'),
			cacheTime: 1000 * 60 * 60 * 24,	// Cached for 24 hours
			urls: data.urls
		});
 
		sitemap.maps.pages.toXML(callback);
	});
};
 
sitemap.getCategories = function (callback) {
	if (
		sitemap.maps.categories &&
		Date.now() < parseInt(sitemap.maps.categories.cacheSetTimestamp, 10) + parseInt(sitemap.maps.categories.cacheResetPeriod, 10)
	) {
		return sitemap.maps.categories.toXML(callback);
	}
 
	var categoryUrls = [];
	categories.getCategoriesByPrivilege('categories:cid', 0, 'find', function (err, categoriesData) {
		if (err) {
			return callback(err);
		}
 
		categoriesData.forEach(function (category) {
			if (category) {
				categoryUrls.push({
					url: '/category/' + category.slug,
					changefreq: 'weekly',
					priority: 0.4
				});
			}
		});
 
		sitemap.maps.categories = sm.createSitemap({
			hostname: nconf.get('url'),
			cacheTime: 1000 * 60 * 60 * 24,	// Cached for 24 hours
			urls: categoryUrls
		});
 
		sitemap.maps.categories.toXML(callback);
	});
};
 
sitemap.getTopicPage = function (page, callback) {
	if (parseInt(page, 10) <= 0) {
		return callback();
	}
 
	var numTopics = parseInt(meta.config.sitemapTopics, 10) || 500;
	var min = (parseInt(page, 10) - 1) * numTopics;
	var max = min + numTopics;
 
	if (
		sitemap.maps.topics[page - 1] &&
		Date.now() < parseInt(sitemap.maps.topics[page - 1].cacheSetTimestamp, 10) + parseInt(sitemap.maps.topics[page - 1].cacheResetPeriod, 10)
	) {
		return sitemap.maps.topics[page - 1].toXML(callback);
	}
 
	var topicUrls = [];
 
	async.waterfall([
		function (next) {
			db.getSortedSetRevRange('topics:recent', min, max, next);
		},
		function (tids, next) {
			privileges.topics.filterTids('read', tids, 0, next);
		},
		function (tids, next) {
			topics.getTopicsFields(tids, ['tid', 'title', 'slug', 'lastposttime'], next);
		}
	], function (err, topics) {
		if (err) {
			return callback(err);
		}
 
		topics.forEach(function (topic) {
			if (topic) {
				topicUrls.push({
					url: '/topic/' + topic.slug,
					lastmodISO: utils.toISOString(topic.lastposttime),
					changefreq: 'daily',
					priority: 0.6
				});
			}
		});
 
		sitemap.maps.topics[page - 1] = sm.createSitemap({
			hostname: nconf.get('url'),
			cacheTime: 1000 * 60 * 60,	// Cached for 1 hour
			urls: topicUrls
		});
 
		sitemap.maps.topics[page - 1].toXML(callback);
	});
};
 
sitemap.clearCache = function () {
	if (sitemap.obj) {
		sitemap.obj.clearCache();
	}
};
 
module.exports = sitemap;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/social.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/social.js

Statements: 2.94% (1 / 34)      Branches: 0% (0 / 10)      Functions: 0% (0 / 12)      Lines: 2.94% (1 / 34)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86    6                                                                                                                                                                      
"use strict";
 
var plugins = require('./plugins');
var db = require('./database');
var async = require('async');
 
var social = {};
 
social.postSharing = null;
 
social.getPostSharing = function (callback) {
	if (social.postSharing) {
		return callback(null, social.postSharing);
	}
 
	var networks = [
		{
			id: "facebook",
			name: "Facebook",
			class: "fa-facebook"
		},
		{
			id: "twitter",
			name: "Twitter",
			class: "fa-twitter"
		},
		{
			id: "google",
			name: "Google+",
			class: "fa-google-plus"
		}
	];
 
	async.waterfall([
		function (next) {
			plugins.fireHook('filter:social.posts', networks, next);
		},
		function (networks, next) {
			db.getSetMembers('social:posts.activated', function (err, activated) {
				if (err) {
					return next(err);
				}
 
				networks.forEach(function (network, i) {
					networks[i].activated = (activated.indexOf(network.id) !== -1);
				});
 
				social.postSharing = networks;
				next(null, networks);
			});
		}
	], callback);
};
 
social.getActivePostSharing = function (callback) {
	social.getPostSharing(function (err, networks) {
		if (err) {
			return callback(err);
		}
		networks = networks.filter(function (network) {
			return network && network.activated;
		});
		callback(null, networks);
	});
};
 
social.setActivePostSharingNetworks = function (networkIDs, callback) {
	async.waterfall([
		function (next) {
			db.delete('social:posts.activated', next);
		},
		function (next) {
			if (!networkIDs.length) {
				return next();
			}
			db.setAdd('social:posts.activated', networkIDs, next);
		},
		function (next) {
			social.postSharing = null;
			next();
		}
	], callback);
};
 
module.exports = social;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics.js

Statements: 2.99% (6 / 201)      Branches: 0% (0 / 75)      Functions: 0% (0 / 68)      Lines: 2.99% (6 / 201)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394    24 24   24                                                                                                                                                                                     1                                                                                                                                                                                                                                                   1                                                                                                                                             1                                                                                                                                                                                                                
"use strict";
 
var async = require('async');
var _ = require('underscore');
 
var db = require('./database');
var posts = require('./posts');
var utils = require('../public/src/utils');
var plugins = require('./plugins');
var user = require('./user');
var categories = require('./categories');
var privileges = require('./privileges');
var social = require('./social');
 
(function (Topics) {
 
	require('./topics/data')(Topics);
	require('./topics/create')(Topics);
	require('./topics/delete')(Topics);
	require('./topics/unread')(Topics);
	require('./topics/recent')(Topics);
	require('./topics/popular')(Topics);
	require('./topics/user')(Topics);
	require('./topics/fork')(Topics);
	require('./topics/posts')(Topics);
	require('./topics/follow')(Topics);
	require('./topics/tags')(Topics);
	require('./topics/teaser')(Topics);
	require('./topics/suggested')(Topics);
	require('./topics/tools')(Topics);
	require('./topics/thumb')(Topics);
 
	Topics.exists = function (tid, callback) {
		db.isSortedSetMember('topics:tid', tid, callback);
	};
 
	Topics.getPageCount = function (tid, uid, callback) {
		Topics.getTopicField(tid, 'postcount', function (err, postCount) {
			if (err) {
				return callback(err);
			}
			if (!parseInt(postCount, 10)) {
				return callback(null, 1);
			}
			user.getSettings(uid, function (err, settings) {
				if (err) {
					return callback(err);
				}
 
				callback(null, Math.ceil((parseInt(postCount, 10) - 1) / settings.postsPerPage));
			});
		});
	};
 
	Topics.getTidPage = function (tid, uid, callback) {
		console.warn('[Topics.getTidPage] deprecated!');
		callback(null, 1);
	};
 
	Topics.getTopicsFromSet = function (set, uid, start, stop, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange(set, start, stop, next);
			},
			function (tids, next) {
				Topics.getTopics(tids, uid, next);
			},
			function (topics, next) {
				next(null, {topics: topics, nextStart: stop + 1});
			}
		], callback);
	};
 
	Topics.getTopics = function (tids, uid, callback) {
		async.waterfall([
			function (next) {
				privileges.topics.filterTids('read', tids, uid, next);
			},
			function (tids, next) {
				Topics.getTopicsByTids(tids, uid, next);
			}
		], callback);
	};
 
	Topics.getTopicsByTids = function (tids, uid, callback) {
		if (!Array.isArray(tids) || !tids.length) {
			return callback(null, []);
		}
 
		var uids, cids, topics;
 
		async.waterfall([
			function (next) {
				Topics.getTopicsData(tids, next);
			},
			function (_topics, next) {
				function mapFilter(array, field) {
					return array.map(function (topic) {
						return topic && topic[field] && topic[field].toString();
					}).filter(function (value, index, array) {
						return utils.isNumber(value) && array.indexOf(value) === index;
					});
				}
 
				topics = _topics;
				uids = mapFilter(topics, 'uid');
				cids = mapFilter(topics, 'cid');
 
				async.parallel({
					users: function (next) {
						user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status'], next);
					},
					categories: function (next) {
						categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'image', 'bgColor', 'color', 'disabled'], next);
					},
					hasRead: function (next) {
						Topics.hasReadTopics(tids, uid, next);
					},
					isIgnored: function (next) {
						Topics.isIgnoring(tids, uid, next);
					},
					bookmarks: function (next) {
						Topics.getUserBookmarks(tids, uid, next);
					},
					teasers: function (next) {
						Topics.getTeasers(topics, next);
					},
					tags: function (next) {
						Topics.getTopicsTagsObjects(tids, next);
					}
				}, next);
			},
			function (results, next) {
				var users = _.object(uids, results.users);
				var categories = _.object(cids, results.categories);
 
				for (var i = 0; i < topics.length; ++i) {
					if (topics[i]) {
						topics[i].category = categories[topics[i].cid];
						topics[i].user = users[topics[i].uid];
						topics[i].teaser = results.teasers[i];
						topics[i].tags = results.tags[i];
 
						topics[i].isOwner = parseInt(topics[i].uid, 10) === parseInt(uid, 10);
						topics[i].pinned = parseInt(topics[i].pinned, 10) === 1;
						topics[i].locked = parseInt(topics[i].locked, 10) === 1;
						topics[i].deleted = parseInt(topics[i].deleted, 10) === 1;
						topics[i].ignored = results.isIgnored[i];
						topics[i].unread = !results.hasRead[i] && !results.isIgnored[i];
						topics[i].bookmark = results.bookmarks[i];
						topics[i].unreplied = !topics[i].teaser;
 
						topics[i].icons = [];
					}
				}
 
				topics = topics.filter(function (topic) {
					return topic &&	topic.category && !topic.category.disabled;
				});
 
				plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, next);
			},
			function (data, next) {
				next(null, data.topics);
			}
		], callback);
	};
 
	Topics.getTopicWithPosts = function (topicData, set, uid, start, stop, reverse, callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, stop, reverse),
					category: async.apply(Topics.getCategoryData, topicData.tid),
					threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
					isFollowing: async.apply(Topics.isFollowing, [topicData.tid], uid),
					isIgnoring: async.apply(Topics.isIgnoring, [topicData.tid], uid),
					bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid),
					postSharing: async.apply(social.getActivePostSharing),
					related: function (next) {
						async.waterfall([
							function (next) {
								Topics.getTopicTagsObjects(topicData.tid, next);
							},
							function (tags, next) {
								topicData.tags = tags;
								Topics.getRelatedTopics(topicData, uid, next);
							}
						], next);
					}
				}, next);
			},
			function (results, next) {
				topicData.posts = results.posts;
				topicData.category = results.category;
				topicData.thread_tools = results.threadTools.tools;
				topicData.isFollowing = results.isFollowing[0];
				topicData.isNotFollowing = !results.isFollowing[0] && !results.isIgnoring[0];
				topicData.isIgnoring = results.isIgnoring[0];
				topicData.bookmark = results.bookmark;
				topicData.postSharing = results.postSharing;
				topicData.related = results.related || [];
 
				topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
				topicData.deleted = parseInt(topicData.deleted, 10) === 1;
				topicData.locked = parseInt(topicData.locked, 10) === 1;
				topicData.pinned = parseInt(topicData.pinned, 10) === 1;
 
				topicData.icons = [];
 
				plugins.fireHook('filter:topic.get', {topic: topicData, uid: uid}, next);
			},
			function (data, next) {
				next(null, data.topic);
			}
		], callback);
	};
 
	function getMainPostAndReplies(topic, set, uid, start, stop, reverse, callback) {
		async.waterfall([
			function (next) {
				if (stop > 0) {
					stop--;
					if (start > 0) {
						start --;
					}
				}
 
				posts.getPidsFromSet(set, start, stop, reverse, next);
			},
			function (pids, next) {
				if ((!Array.isArray(pids) || !pids.length) && !topic.mainPid) {
					return callback(null, []);
				}
 
				if (topic.mainPid && start === 0) {
					pids.unshift(topic.mainPid);
				}
				posts.getPostsByPids(pids, uid, next);
			},
			function (posts, next) {
				if (!posts.length) {
					return next(null, []);
				}
				var replies = posts;
				if (topic.mainPid && start === 0) {
					posts[0].index = 0;
					replies = posts.slice(1);
				}
 
				Topics.calculatePostIndices(replies, start, stop, topic.postcount, reverse);
 
				Topics.addPostData(posts, uid, next);
			}
		], callback);
	}
 
	Topics.getMainPost = function (tid, uid, callback) {
		Topics.getMainPosts([tid], uid, function (err, mainPosts) {
			callback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null);
		});
	};
 
	Topics.getMainPids = function (tids, callback) {
		if (!Array.isArray(tids) || !tids.length) {
			return callback(null, []);
		}
 
		Topics.getTopicsFields(tids, ['mainPid'], function (err, topicData) {
			if (err) {
				return callback(err);
			}
 
			var mainPids = topicData.map(function (topic) {
				return topic && topic.mainPid;
			});
			callback(null, mainPids);
		});
	};
 
	Topics.getMainPosts = function (tids, uid, callback) {
		Topics.getMainPids(tids, function (err, mainPids) {
			if (err) {
				return callback(err);
			}
			getMainPosts(mainPids, uid, callback);
		});
	};
 
	function getMainPosts(mainPids, uid, callback) {
		posts.getPostsByPids(mainPids, uid, function (err, postData) {
			if (err) {
				return callback(err);
			}
			postData.forEach(function (post) {
				if (post) {
					post.index = 0;
				}
			});
			Topics.addPostData(postData, uid, callback);
		});
	}
 
	Topics.getUserBookmark = function (tid, uid, callback) {
		db.sortedSetScore('tid:' + tid + ':bookmarks', uid, callback);
	};
 
	Topics.getUserBookmarks = function (tids, uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, tids.map(function () {
				return null;
			}));
		}
		db.sortedSetsScore(tids.map(function (tid) {
			return 'tid:' + tid + ':bookmarks';
		}), uid, callback);
	};
 
	Topics.setUserBookmark = function (tid, uid, index, callback) {
		db.sortedSetAdd('tid:' + tid + ':bookmarks', index, uid, callback);
	};
 
	Topics.isLocked = function (tid, callback) {
		Topics.getTopicField(tid, 'locked', function (err, locked) {
			callback(err, parseInt(locked, 10) === 1);
		});
	};
 
	Topics.search = function (tid, term, callback) {
		if (plugins.hasListeners('filter:topic.search')) {
			plugins.fireHook('filter:topic.search', {
				tid: tid,
				term: term
			}, callback);
		} else {
			callback(new Error('no-plugins-available'), []);
		}
	};
 
	Topics.getTopicBookmarks = function (tid, callback) {
		db.getSortedSetRangeWithScores(['tid:' + tid + ':bookmarks'], 0, -1, callback);
	};
 
	Topics.updateTopicBookmarks = function (tid, pids, callback) {
		var maxIndex;
 
		async.waterfall([
			function (next) {
				Topics.getPostCount(tid, next);
			},
			function (postcount, next) {
				maxIndex = postcount;
				Topics.getTopicBookmarks(tid, next);
			},
			function (bookmarks, next) {
				var forkedPosts = pids.map(function (pid) {
					return {pid: pid, tid: tid};
				});
 
				var uidData = bookmarks.map(function (bookmark) {
					return {
						uid: bookmark.value,
						bookmark: bookmark.score
					};
				});
 
				async.eachLimit(uidData, 50, function (data, next) {
					posts.getPostIndices(forkedPosts, data.uid, function (err, postIndices) {
						if (err) {
							return next(err);
						}
 
						var bookmark = data.bookmark;
						bookmark = bookmark < maxIndex ? bookmark : maxIndex;
 
						for (var i = 0; i < postIndices.length && postIndices[i] < data.bookmark; ++i) {
							--bookmark;
						}
 
						if (parseInt(bookmark, 10) !== parseInt(data.bookmark, 10)) {
							Topics.setUserBookmark(tid, data.uid, bookmark, next);
						} else {
							next();
						}
					});
				}, next);
			}
		], function (err) {
			callback(err);
		});
	};
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/upgrade.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/upgrade.js

Statements: 0.7% (4 / 571)      Branches: 0% (0 / 242)      Functions: 0% (0 / 157)      Lines: 0.7% (4 / 571)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095        2                                                                                                                                                                           1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           1                                                       1                                                                                                                                                                                                                                                                                                                                                                                                                                    
"use strict";
 
/* globals console, require */
 
var db = require('./database'),
	async = require('async'),
	winston = require('winston'),
 
	Upgrade = {},
 
	minSchemaDate = Date.UTC(2015, 10, 6),		// This value gets updated every new MAJOR version
	schemaDate, thisSchemaDate,
 
	// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
	latestSchema = Date.UTC(2016, 10, 22);
 
Upgrade.check = function (callback) {
	db.get('schemaDate', function (err, value) {
		if (err) {
			return callback(err);
		}
 
		if (!value) {
			db.set('schemaDate', latestSchema, function (err) {
				if (err) {
					return callback(err);
				}
				callback(null);
			});
			return;
		}
 
		var schema_ok = parseInt(value, 10) >= latestSchema;
		callback(!schema_ok ? new Error('schema-out-of-date') : null);
	});
};
 
Upgrade.update = function (schemaDate, callback) {
	db.set('schemaDate', schemaDate, callback);
};
 
Upgrade.upgrade = function (callback) {
	var updatesMade = false;
 
	winston.info('Beginning database schema update');
 
	async.series([
		function (next) {
			// Prepare for upgrade & check to make sure the upgrade is possible
			db.get('schemaDate', function (err, value) {
				if (err) {
					return next(err);
				}
 
				if(!value) {
					db.set('schemaDate', latestSchema, function () {
						next();
					});
					schemaDate = latestSchema;
				} else {
					schemaDate = parseInt(value, 10);
				}
 
				if (schemaDate >= minSchemaDate) {
					next();
				} else {
					next(new Error('upgrade-not-possible'));
				}
			});
		},
		function (next) {
			thisSchemaDate = Date.UTC(2015, 11, 15);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2015/12/15] Upgrading chats');
 
				db.getObjectFields('global', ['nextMid', 'nextChatRoomId'], function (err, globalData) {
					if (err) {
						return next(err);
					}
 
					var rooms = {};
					var roomId = globalData.nextChatRoomId || 1;
					var currentMid = 1;
 
					async.whilst(function () {
						return currentMid <= globalData.nextMid;
					}, function (next) {
						db.getObject('message:' + currentMid, function (err, message) {
							function addMessageToUids(roomId, callback) {
								async.parallel([
									function (next) {
										db.sortedSetAdd('uid:' + message.fromuid + ':chat:room:' + roomId + ':mids', msgTime, currentMid, next);
									},
									function (next) {
										db.sortedSetAdd('uid:' + message.touid + ':chat:room:' + roomId + ':mids', msgTime, currentMid, next);
									}
								], callback);
							}
 
							if (err || !message)  {
								winston.info('skipping chat message ', currentMid);
								currentMid ++;
								return next(err);
							}
 
							var pairID = [parseInt(message.fromuid, 10), parseInt(message.touid, 10)].sort().join(':');
							var msgTime = parseInt(message.timestamp, 10);
 
							if (rooms[pairID]) {
								winston.info('adding message ' + currentMid + ' to existing roomID ' + roomId);
								addMessageToUids(rooms[pairID], function (err) {
									if (err) {
										return next(err);
									}
									currentMid ++;
									next();
								});
							} else {
								winston.info('adding message ' + currentMid + ' to new roomID ' + roomId);
								async.parallel([
									function (next) {
										db.sortedSetAdd('uid:' + message.fromuid + ':chat:rooms', msgTime, roomId, next);
									},
									function (next) {
										db.sortedSetAdd('uid:' + message.touid + ':chat:rooms', msgTime, roomId, next);
									},
									function (next) {
										db.sortedSetAdd('chat:room:' + roomId + ':uids', [msgTime, msgTime + 1], [message.fromuid, message.touid], next);
									},
									function (next) {
										addMessageToUids(roomId, next);
									}
								], function (err) {
									if (err) {
										return next(err);
									}
									rooms[pairID] = roomId;
									roomId ++;
									currentMid ++;
									db.setObjectField('global', 'nextChatRoomId', roomId, next);
								});
							}
						});
					}, function (err) {
						if (err) {
							return next(err);
						}
 
						winston.info('[2015/12/15] Chats upgrade done!');
						Upgrade.update(thisSchemaDate, next);
					});
				});
			} else {
				winston.info('[2015/12/15] Chats upgrade skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2015, 11, 23);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2015/12/23] Upgrading chat room hashes');
 
				db.getObjectField('global', 'nextChatRoomId', function (err, nextChatRoomId) {
					if (err) {
						return next(err);
					}
					var currentChatRoomId = 1;
					async.whilst(function () {
						return currentChatRoomId <= nextChatRoomId;
					}, function (next) {
						db.getSortedSetRange('chat:room:' + currentChatRoomId + ':uids', 0, 0, function (err, uids) {
							if (err) {
								return next(err);
							}
							if (!Array.isArray(uids) || !uids.length || !uids[0]) {
								++ currentChatRoomId;
								return next();
							}
 
							db.setObject('chat:room:' + currentChatRoomId, {owner: uids[0], roomId: currentChatRoomId}, function (err) {
								if (err) {
									return next(err);
								}
								++ currentChatRoomId;
								next();
							});
						});
					}, function (err) {
						if (err) {
							return next(err);
						}
 
						winston.info('[2015/12/23] Chats room hashes upgrade done!');
						Upgrade.update(thisSchemaDate, next);
					});
				});
			} else {
				winston.info('[2015/12/23] Chats room hashes upgrade skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 0, 11);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2015/12/23] Adding theme to active plugins sorted set');
 
				async.waterfall([
					async.apply(db.getObjectField, 'config', 'theme:id'),
					async.apply(db.sortedSetAdd, 'plugins:active', 0)
				], function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2015/12/23] Adding theme to active plugins sorted set done!');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2015/12/23] Adding theme to active plugins sorted set skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 0, 14);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/01/14] Creating user best post sorted sets');
 
				var batch = require('./batch');
 
				batch.processSortedSet('posts:pid', function (ids, next) {
					async.eachSeries(ids, function (id, next) {
						db.getObjectFields('post:' + id, ['pid', 'uid', 'votes'], function (err, postData) {
							if (err) {
								return next(err);
							}
							if (!postData || !parseInt(postData.votes, 10) || !parseInt(postData.uid, 10)) {
								return next();
							}
							winston.info('processing pid: ' + postData.pid + ' uid: ' + postData.uid + ' votes: ' + postData.votes);
							db.sortedSetAdd('uid:' + postData.uid + ':posts:votes', postData.votes, postData.pid, next);
						});
					}, next);
				}, {}, function (err) {
					if (err) {
						return next(err);
					}
					winston.info('[2016/01/14] Creating user best post sorted sets done!');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/01/14] Creating user best post sorted sets skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 0, 20);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/01/20] Creating users:notvalidated');
 
				var batch = require('./batch');
				var now = Date.now();
				batch.processSortedSet('users:joindate', function (ids, next) {
					async.eachSeries(ids, function (id, next) {
						db.getObjectFields('user:' + id, ['uid', 'email:confirmed'], function (err, userData) {
							if (err) {
								return next(err);
							}
							if (!userData || !parseInt(userData.uid, 10) || parseInt(userData['email:confirmed'], 10) === 1) {
								return next();
							}
							winston.info('processing uid: ' + userData.uid + ' email:confirmed: ' + userData['email:confirmed']);
							db.sortedSetAdd('users:notvalidated', now, userData.uid, next);
						});
					}, next);
				}, {}, function (err) {
					if (err) {
						return next(err);
					}
					winston.info('[2016/01/20] Creating users:notvalidated done!');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/01/20] Creating users:notvalidated skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 0, 23);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/01/23] Creating Global moderators group');
 
				var groups = require('./groups');
				async.waterfall([
					function (next) {
						groups.exists('Global Moderators', next);
					},
					function (exists, next) {
						if (exists) {
							return next(null, null);
						}
						groups.create({
							name: 'Global Moderators',
							userTitle: 'Global Moderator',
							description: 'Forum wide moderators',
							hidden: 0,
							private: 1,
							disableJoinRequests: 1
						}, next);
					},
					function (groupData, next) {
						groups.show('Global Moderators', next);
					}
				], function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/01/23] Creating Global moderators group done!');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/01/23] Creating Global moderators group skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 1, 25);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/02/25] Social: Post Sharing');
 
				var social = require('./social');
				async.parallel([
					function (next) {
						social.setActivePostSharingNetworks(['facebook', 'google', 'twitter'], next);
					},
					function (next) {
						db.deleteObjectField('config', 'disableSocialButtons', next);
					}
				], function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/02/25] Social: Post Sharing done!');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/02/25] Social: Post Sharing skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 3, 14);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/04/14] Group title from settings to user profile');
 
				var user = require('./user');
				var batch = require('./batch');
				var count = 0;
				batch.processSortedSet('users:joindate', function (uids, next) {
					winston.info('upgraded ' + count + ' users');
					user.getMultipleUserSettings(uids, function (err, settings) {
						if (err) {
							return next(err);
						}
						count += uids.length;
						settings = settings.filter(function (setting) {
							return setting && setting.groupTitle;
						});
 
						async.each(settings, function (setting, next) {
							db.setObjectField('user:' + setting.uid, 'groupTitle', setting.groupTitle, next);
						}, next);
					});
				}, {}, function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/04/14] Group title from settings to user profile done');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/04/14] Group title from settings to user profile skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 3, 18);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/04/19] Users post count per tid');
 
				var batch = require('./batch');
				var topics = require('./topics');
				var count = 0;
				batch.processSortedSet('topics:tid', function (tids, next) {
					winston.info('upgraded ' + count + ' topics');
					count += tids.length;
					async.each(tids, function (tid, next) {
						db.delete('tid:' + tid + ':posters', function (err) {
							if (err) {
								return next(err);
							}
							topics.getPids(tid, function (err, pids) {
								if (err) {
									return next(err);
								}
 
								if (!pids.length) {
									return next();
								}
 
								async.eachSeries(pids, function (pid, next) {
									db.getObjectField('post:' + pid, 'uid', function (err, uid) {
										if (err) {
											return next(err);
										}
										if (!parseInt(uid, 10)) {
											return next();
										}
										db.sortedSetIncrBy('tid:' + tid + ':posters', 1, uid, next);
									});
								}, next);
							});
						});
					}, next);
				}, {}, function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/04/19] Users post count per tid done');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/04/19] Users post count per tid skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 3, 29);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/04/29] Dismiss flags from deleted topics');
 
				var posts = require('./posts'),
					topics = require('./topics');
 
				var pids, tids;
 
				async.waterfall([
					async.apply(db.getSortedSetRange, 'posts:flagged', 0, -1),
					function (_pids, next) {
						pids = _pids;
						posts.getPostsFields(pids, ['tid'], next);
					},
					function (_tids, next) {
						tids = _tids.map(function (a) {
							return a.tid;
						});
 
						topics.getTopicsFields(tids, ['deleted'], next);
					},
					function (state, next) {
						var toDismiss = state.map(function (a, idx) {
							return parseInt(a.deleted, 10) === 1 ? pids[idx] : null;
						}).filter(Boolean);
 
						winston.info('[2016/04/29] ' + toDismiss.length + ' dismissable flags found');
						async.each(toDismiss, posts.dismissFlag, next);
					}
				], function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/04/29] Dismiss flags from deleted topics done');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/04/29] Dismiss flags from deleted topics skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 4, 28);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/05/28] Giving topics:read privs to any group that was previously allowed to Find & Access Category');
 
				var groupsAPI = require('./groups');
				var privilegesAPI = require('./privileges');
 
				db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
					if (err) {
						return next(err);
					}
 
					async.eachSeries(cids, function (cid, next) {
						privilegesAPI.categories.list(cid, function (err, data) {
							if (err) {
								return next(err);
							}
 
							var groups = data.groups;
							var users = data.users;
 
							async.waterfall([
								function (next) {
									async.eachSeries(groups, function (group, next) {
										if (group.privileges['groups:read']) {
											return groupsAPI.join('cid:' + cid + ':privileges:groups:topics:read', group.name, function (err) {
												if (!err) {
													winston.info('cid:' + cid + ':privileges:groups:topics:read granted to gid: ' + group.name);
												}
 
												return next(err);
											});
										}
 
										next(null);
									}, next);
								},
								function (next) {
									async.eachSeries(users, function (user, next) {
										if (user.privileges.read) {
											return groupsAPI.join('cid:' + cid + ':privileges:topics:read', user.uid, function (err) {
												if (!err) {
													winston.info('cid:' + cid + ':privileges:topics:read granted to uid: ' + user.uid);
												}
 
												return next(err);
											});
										}
 
										next(null);
									}, next);
								}
							], function (err) {
								if (!err) {
									winston.info('-- cid ' + cid + ' upgraded');
								}
 
								next(err);
							});
						});
					}, function (err) {
						if (err) {
							return next(err);
						}
 
						winston.info('[2016/05/28] Giving topics:read privs to any group that was previously allowed to Find & Access Category - done');
						Upgrade.update(thisSchemaDate, next);
					});
				});
			} else {
				winston.info('[2016/05/28] Giving topics:read privs to any group that was previously allowed to Find & Access Category - skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 5, 13);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/06/13] Store upvotes/downvotes separately');
 
				var batch = require('./batch');
				var posts = require('./posts');
				var count = 0;
				batch.processSortedSet('posts:pid', function (pids, next) {
					winston.info('upgraded ' + count + ' posts');
					count += pids.length;
					async.each(pids, function (pid, next) {
						async.parallel({
							upvotes: function (next) {
								db.setCount('pid:' + pid + ':upvote', next);
							},
							downvotes: function (next) {
								db.setCount('pid:' + pid + ':downvote', next);
							}
						}, function (err, results) {
							if (err) {
								return next(err);
							}
							var data = {};
 
							if (parseInt(results.upvotes, 10) > 0) {
								data.upvotes = results.upvotes;
							}
							if (parseInt(results.downvotes, 10) > 0) {
								data.downvotes = results.downvotes;
							}
 
							if (Object.keys(data).length) {
								posts.setPostFields(pid, data, next);
							} else {
								next();
							}
						}, next);
					}, next);
				}, {}, function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/06/13] Store upvotes/downvotes separately done');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/06/13] Store upvotes/downvotes separately skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 6, 12);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/07/12] Giving upload privileges');
				var privilegesAPI = require('./privileges');
				var meta = require('./meta');
 
				db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
					if (err) {
						return next(err);
					}
 
					async.eachSeries(cids, function (cid, next) {
						privilegesAPI.categories.list(cid, function (err, data) {
							if (err) {
								return next(err);
							}
							async.eachSeries(data.groups, function (group, next) {
								if (group.name === 'guests' && parseInt(meta.config.allowGuestUploads, 10) !== 1) {
									return next();
								}
								if (group.privileges['groups:read']) {
									privilegesAPI.categories.give(['upload:post:image'], cid, group.name, next);
								} else {
									next();
								}
							}, next);
						});
					}, function (err) {
						if (err) {
							return next(err);
						}
 
						winston.info('[2016/07/12] Upload privileges done');
						Upgrade.update(thisSchemaDate, next);
					});
				});
			} else {
				winston.info('[2016/07/12] Upload privileges skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 7, 5);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/08/05] Removing best posts with negative scores');
				var batch = require('./batch');
				batch.processSortedSet('users:joindate', function (ids, next) {
					async.each(ids, function (id, next) {
						console.log('processing uid ' + id);
						db.sortedSetsRemoveRangeByScore(['uid:' + id + ':posts:votes'], '-inf', 0, next);
					}, next);
				}, {}, function (err) {
					if (err) {
						return next(err);
					}
					winston.info('[2016/08/05] Removing best posts with negative scores done!');
					Upgrade.update(thisSchemaDate, next);
				});
 
			} else {
				winston.info('[2016/08/05] Removing best posts with negative scores skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 8, 7);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/08/07] Granting edit/delete/delete topic on existing categories');
 
				var groupsAPI = require('./groups');
				var privilegesAPI = require('./privileges');
 
				db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
					if (err) {
						return next(err);
					}
 
					async.eachSeries(cids, function (cid, next) {
						privilegesAPI.categories.list(cid, function (err, data) {
							if (err) {
								return next(err);
							}
 
							var groups = data.groups;
							var users = data.users;
 
							async.waterfall([
								function (next) {
									async.eachSeries(groups, function (group, next) {
										if (group.privileges['groups:topics:reply']) {
											return async.parallel([
												async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:posts:edit', group.name),
												async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:posts:delete', group.name)
											], function (err) {
												if (!err) {
													winston.info('cid:' + cid + ':privileges:groups:posts:edit, cid:' + cid + ':privileges:groups:posts:delete granted to gid: ' + group.name);
												}
 
												return next(err);
											});
										}
 
										next(null);
									}, next);
								},
								function (next) {
									async.eachSeries(groups, function (group, next) {
										if (group.privileges['groups:topics:create']) {
											return groupsAPI.join('cid:' + cid + ':privileges:groups:topics:delete', group.name, function (err) {
												if (!err) {
													winston.info('cid:' + cid + ':privileges:groups:topics:delete granted to gid: ' + group.name);
												}
 
												return next(err);
											});
										}
 
										next(null);
									}, next);
								},
								function (next) {
									async.eachSeries(users, function (user, next) {
										if (user.privileges['topics:reply']) {
											return async.parallel([
												async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:posts:edit', user.uid),
												async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:posts:delete', user.uid)
											], function (err) {
												if (!err) {
													winston.info('cid:' + cid + ':privileges:posts:edit, cid:' + cid + ':privileges:posts:delete granted to uid: ' + user.uid);
												}
 
												return next(err);
											});
										}
 
										next(null);
									}, next);
								},
								function (next) {
									async.eachSeries(users, function (user, next) {
										if (user.privileges['topics:create']) {
											return groupsAPI.join('cid:' + cid + ':privileges:topics:delete', user.uid, function (err) {
												if (!err) {
													winston.info('cid:' + cid + ':privileges:topics:delete granted to uid: ' + user.uid);
												}
 
												return next(err);
											});
										}
 
										next(null);
									}, next);
								}
							], function (err) {
								if (!err) {
									winston.info('-- cid ' + cid + ' upgraded');
								}
 
								next(err);
							});
						});
					}, function (err) {
						if (err) {
							return next(err);
						}
 
						winston.info('[2016/08/07] Granting edit/delete/delete topic on existing categories - done');
						Upgrade.update(thisSchemaDate, next);
					});
				});
			} else {
				winston.info('[2016/08/07] Granting edit/delete/delete topic on existing categories - skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 8, 22);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/09/22] Setting category recent tids');
 
 
				db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
					if (err) {
						return next(err);
					}
 
					async.eachSeries(cids, function (cid, next) {
						db.getSortedSetRevRange('cid:' + cid + ':pids', 0, 0, function (err, pid) {
							if (err || !pid) {
								return next(err);
							}
							db.getObjectFields('post:' + pid, ['tid', 'timestamp'], function (err, postData) {
								if (err || !postData || !postData.tid) {
									return next(err);
								}
								db.sortedSetAdd('cid:' + cid + ':recent_tids', postData.timestamp, postData.tid, next);
							});
						});
					}, function (err) {
						if (err) {
							return next(err);
						}
 
						winston.info('[2016/09/22] Setting category recent tids - done');
						Upgrade.update(thisSchemaDate, next);
					});
				});
			} else {
				winston.info('[2016/09/22] Setting category recent tids - skipped!');
				next();
			}
		},
		function (next) {
			function upgradePosts(next) {
				var batch = require('./batch');
 
				batch.processSortedSet('posts:pid', function (ids, next) {
					async.each(ids, function (id, next) {
						console.log('processing pid ' + id);
						async.waterfall([
							function (next) {
								db.rename('pid:' + id + ':users_favourited', 'pid:' + id + ':users_bookmarked', next);
							},
							function (next) {
								db.getObjectField('post:' + id, 'reputation', next);
							},
							function (reputation, next) {
								if (parseInt(reputation, 10)) {
									db.setObjectField('post:' + id, 'bookmarks', reputation, next);
								} else {
									next();
								}
							},
							function (next) {
								db.deleteObjectField('post:' + id, 'reputation', next);
							}
						], next);
					}, next);
				}, {}, next);
			}
 
			function upgradeUsers(next) {
				var batch = require('./batch');
 
				batch.processSortedSet('users:joindate', function (ids, next) {
					async.each(ids, function (id, next) {
						console.log('processing uid ' + id);
						db.rename('uid:' + id + ':favourites', 'uid:' + id + ':bookmarks', next);
					}, next);
				}, {}, next);
			}
 
			thisSchemaDate = Date.UTC(2016, 9, 8);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/10/8] favourite -> bookmark refactor');
				async.series([upgradePosts, upgradeUsers], function (err) {
					if (err) {
						return next(err);
					}
					winston.info('[2016/08/05] favourite- bookmark refactor done!');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/10/8] favourite -> bookmark refactor - skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 9, 14);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/10/14] Creating sorted sets for post replies');
 
				var posts = require('./posts');
				var batch = require('./batch');
				batch.processSortedSet('posts:pid', function (ids, next) {
					posts.getPostsFields(ids, ['pid', 'toPid', 'timestamp'], function (err, data) {
						if (err) {
							return next(err);
						}
 
						async.eachSeries(data, function (postData, next) {
							if (!parseInt(postData.toPid, 10)) {
								return next(null);
							}
							console.log('processing pid: ' + postData.pid + ' toPid: ' + postData.toPid);
							async.parallel([
								async.apply(db.sortedSetAdd, 'pid:' + postData.toPid + ':replies', postData.timestamp, postData.pid),
								async.apply(db.incrObjectField, 'post:' + postData.toPid, 'replies')
							], next);
						}, next);
					});
				}, function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/10/14] Creating sorted sets for post replies - done');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/10/14] Creating sorted sets for post replies - skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 10, 22);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/11/22] Update global and user language keys');
 
				var user = require('./user');
				var meta = require('./meta');
				var batch = require('./batch');
				var newLanguage;
				var i = 0;
				var j = 0;
				async.parallel([
					function (next) {
						meta.configs.get('defaultLang', function (err, defaultLang) {
							if (err) {
								return next(err);
							}
 
							if (!defaultLang) {
								return setImmediate(next);
							}
 
							newLanguage = defaultLang.replace('_', '-').replace('@', '-x-');
							if (newLanguage !== defaultLang) {
								meta.configs.set('defaultLang', newLanguage, next);
							} else {
								setImmediate(next);
							}
						});
					},
					function (next) {
						batch.processSortedSet('users:joindate', function (ids, next) {
							async.each(ids, function (uid, next) {
								async.waterfall([
									async.apply(db.getObjectField, 'user:' + uid + ':settings', 'userLang'),
									function (language, next) {
										++i;
										if (!language) {
											return setImmediate(next);
										}
 
										newLanguage = language.replace('_', '-').replace('@', '-x-');
										if (newLanguage !== language) {
											++j;
											user.setSetting(uid, 'userLang', newLanguage, next);
										} else {
											setImmediate(next);
										}
									}
								], next);
							}, next);
						}, next);
					}
				], function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/11/22] Update global and user language keys - done (' + i + ' processed, ' + j + ' updated)');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/11/22] Update global and user language keys - skipped!');
				next();
			}
		},
		function (next) {
			thisSchemaDate = Date.UTC(2016, 10, 25);
 
			if (schemaDate < thisSchemaDate) {
				updatesMade = true;
				winston.info('[2016/11/25] Creating sorted sets for pinned topcis');
 
				var topics = require('./topics');
				var batch = require('./batch');
				batch.processSortedSet('topics:tid', function (ids, next) {
					topics.getTopicsFields(ids, ['tid', 'cid', 'pinned', 'lastposttime'], function (err, data) {
						if (err) {
							return next(err);
						}
 
						data = data.filter(function (topicData) {
							return parseInt(topicData.pinned, 10) === 1;
						});
 
						async.eachSeries(data, function (topicData, next) {
							console.log('processing tid: ' + topicData.tid);
 
							async.parallel([
								async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:pinned', Date.now(), topicData.tid),
								async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids', topicData.tid),
								async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:posts', topicData.tid)
							], next);
						}, next);
					});
				}, function (err) {
					if (err) {
						return next(err);
					}
 
					winston.info('[2016/11/25] Creating sorted sets for pinned topics - done');
					Upgrade.update(thisSchemaDate, next);
				});
			} else {
				winston.info('[2016/11/25] Creating sorted sets for pinned topics - skipped!');
				next();
			}
		},
		// Add new schema updates here
		// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!!
	], function (err) {
		if (!err) {
			if(updatesMade) {
				winston.info('[upgrade] Schema update complete!');
			} else {
				winston.info('[upgrade] Schema already up to date!');
			}
		} else {
			switch(err.message) {
			case 'upgrade-not-possible':
				winston.error('[upgrade] NodeBB upgrade could not complete, as your database schema is too far out of date.');
				winston.error('[upgrade]   Please ensure that you did not skip any minor version upgrades.');
				winston.error('[upgrade]   (e.g. v0.1.x directly to v0.3.x)');
				break;
 
			default:
				winston.error('[upgrade] Errors were encountered while updating the NodeBB schema: ' + err.message);
				break;
			}
		}
 
		if (typeof callback === 'function') {
			callback(err);
		} else {
			process.exit();
		}
	});
};
 
module.exports = Upgrade;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user.js

Statements: 2.08% (4 / 192)      Branches: 0% (0 / 73)      Functions: 0% (0 / 71)      Lines: 2.08% (4 / 192)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377    84 84   84 12                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
'use strict';
 
var	async = require('async');
var _ = require('underscore');
 
var groups = require('./groups');
var plugins = require('./plugins');
var db = require('./database');
var topics = require('./topics');
var privileges = require('./privileges');
var meta = require('./meta');
 
(function (User) {
 
	User.email = require('./user/email');
	User.notifications = require('./user/notifications');
	User.reset = require('./user/reset');
	User.digest = require('./user/digest');
 
	require('./user/data')(User);
	require('./user/auth')(User);
	require('./user/bans')(User);
	require('./user/create')(User);
	require('./user/posts')(User);
	require('./user/topics')(User);
	require('./user/categories')(User);
	require('./user/follow')(User);
	require('./user/profile')(User);
	require('./user/admin')(User);
	require('./user/delete')(User);
	require('./user/settings')(User);
	require('./user/search')(User);
	require('./user/jobs')(User);
	require('./user/picture')(User);
	require('./user/approval')(User);
	require('./user/invite')(User);
	require('./user/password')(User);
	require('./user/info')(User);
 
	User.updateLastOnlineTime = function (uid, callback) {
		callback = callback || function () {};
		db.getObjectFields('user:' + uid, ['status', 'lastonline'], function (err, userData) {
			var now = Date.now();
			if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) {
				return callback(err);
			}
			User.setUserField(uid, 'lastonline', now, callback);
		});
	};
 
	User.updateOnlineUsers = function (uid, callback) {
		callback = callback || function () {};
 
		var now = Date.now();
		async.waterfall([
			function (next) {
				db.sortedSetScore('users:online', uid, next);
			},
			function (userOnlineTime, next) {
				if (now - parseInt(userOnlineTime, 10) < 300000) {
					return callback();
				}
				db.sortedSetAdd('users:online', now, uid, next);
			},
			function (next) {
				topics.pushUnreadCount(uid);
				plugins.fireHook('action:user.online', {uid: uid, timestamp: now});
				next();
			}
		], callback);
	};
 
	User.getUidsFromSet = function (set, start, stop, callback) {
		if (set === 'users:online') {
			var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
			var now = Date.now();
			db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - 300000, callback);
		} else {
			db.getSortedSetRevRange(set, start, stop, callback);
		}
	};
 
	User.getUsersFromSet = function (set, uid, start, stop, callback) {
		async.waterfall([
			function (next) {
				User.getUidsFromSet(set, start, stop, next);
			},
			function (uids, next) {
				User.getUsers(uids, uid, next);
			}
		], callback);
	};
 
	User.getUsersWithFields = function (uids, fields, uid, callback) {
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:users.addFields', {fields: fields}, next);
			},
			function (data, next) {
				data.fields = data.fields.filter(function (field, index, array) {
					return array.indexOf(field) === index;
				});
 
				async.parallel({
					userData: function (next) {
						User.getUsersFields(uids, data.fields, next);
					},
					isAdmin: function (next) {
						User.isAdministrator(uids, next);
					}
				}, next);
			},
			function (results, next) {
				results.userData.forEach(function (user, index) {
					if (user) {
						user.status = User.getStatus(user);
						user.administrator = results.isAdmin[index];
						user.banned = parseInt(user.banned, 10) === 1;
						user.banned_until = parseInt(user['banned:expire'], 10) || 0;
						user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned';
						user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1;
					}
				});
				plugins.fireHook('filter:userlist.get', {users: results.userData, uid: uid}, next);
			},
			function (data, next) {
				next(null, data.users);
			}
		], callback);
	};
 
	User.getUsers = function (uids, uid, callback) {
		var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags',
			'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline'];
 
		User.getUsersWithFields(uids, fields, uid, callback);
	};
 
	User.getStatus = function (userData) {
		var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000;
		return isOnline ? (userData.status || 'online') : 'offline';
	};
 
	User.isOnline = function (uid, callback) {
		if (Array.isArray(uid)) {
			db.sortedSetScores('users:online', uid, function (err, lastonline) {
				if (err) {
					return callback(err);
				}
				var now = Date.now();
				var isOnline = uid.map(function (uid, index) {
					return now - lastonline[index] < 300000;
				});
				callback(null, isOnline);
			});
		} else {
			db.sortedSetScore('users:online', uid, function (err, lastonline) {
				if (err) {
					return callback(err);
				}
				var isOnline = Date.now() - parseInt(lastonline, 10) < 300000;
				callback(null, isOnline);
			});
		}
 
	};
 
	User.exists = function (uid, callback) {
		db.isSortedSetMember('users:joindate', uid, callback);
	};
 
	User.existsBySlug = function (userslug, callback) {
		User.getUidByUserslug(userslug, function (err, exists) {
			callback(err, !! exists);
		});
	};
 
	User.getUidByUsername = function (username, callback) {
		if (!username) {
			return callback(null, 0);
		}
		db.sortedSetScore('username:uid', username, callback);
	};
 
	User.getUidsByUsernames = function (usernames, callback) {
		db.sortedSetScores('username:uid', usernames, callback);
	};
 
	User.getUidByUserslug = function (userslug, callback) {
		if (!userslug) {
			return callback(null, 0);
		}
		db.sortedSetScore('userslug:uid', userslug, callback);
	};
 
	User.getUsernamesByUids = function (uids, callback) {
		User.getUsersFields(uids, ['username'], function (err, users) {
			if (err) {
				return callback(err);
			}
 
			users = users.map(function (user) {
				return user.username;
			});
 
			callback(null, users);
		});
	};
 
	User.getUsernameByUserslug = function (slug, callback) {
		async.waterfall([
			function (next) {
				User.getUidByUserslug(slug, next);
			},
			function (uid, next) {
				User.getUserField(uid, 'username', next);
			}
		], callback);
	};
 
	User.getUidByEmail = function (email, callback) {
		db.sortedSetScore('email:uid', email.toLowerCase(), callback);
	};
 
	User.getUidsByEmails = function (emails, callback) {
		emails = emails.map(function (email) {
			return email && email.toLowerCase();
		});
		db.sortedSetScores('email:uid', emails, callback);
	};
 
	User.getUsernameByEmail = function (email, callback) {
		db.sortedSetScore('email:uid', email.toLowerCase(), function (err, uid) {
			if (err) {
				return callback(err);
			}
			User.getUserField(uid, 'username', callback);
		});
	};
 
	User.isModerator = function (uid, cid, callback) {
		privileges.users.isModerator(uid, cid, callback);
	};
 
	User.isModeratorOfAnyCategory = function (uid, callback) {
		User.getModeratedCids(uid, function (err, cids) {
			callback(err, Array.isArray(cids) ? !!cids.length : false);
		});
	};
 
	User.isAdministrator = function (uid, callback) {
		privileges.users.isAdministrator(uid, callback);
	};
 
	User.isGlobalModerator = function (uid, callback) {
		privileges.users.isGlobalModerator(uid, callback);
	};
 
	User.isAdminOrGlobalMod = function (uid, callback) {
		async.parallel({
			isAdmin: async.apply(User.isAdministrator, uid),
			isGlobalMod: async.apply(User.isGlobalModerator, uid)
		}, function (err, results) {
			callback(err, results ? (results.isAdmin || results.isGlobalMod) : false);
		});
	};
 
	User.isAdminOrSelf = function (callerUid, uid, callback) {
		if (parseInt(callerUid, 10) === parseInt(uid, 10)) {
			return callback();
		}
		User.isAdministrator(callerUid, function (err, isAdmin) {
			if (err || !isAdmin) {
				return callback(err || new Error('[[error:no-privileges]]'));
			}
			callback();
		});
	};
 
	User.getAdminsandGlobalMods = function (callback) {
		async.parallel({
			admins: async.apply(groups.getMembers, 'administrators', 0, -1),
			mods: async.apply(groups.getMembers, 'Global Moderators', 0, -1)
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var uids = results.admins.concat(results.mods).filter(function (uid, index, array) {
				return uid && array.indexOf(uid) === index;
			});
			User.getUsersData(uids, callback);
		});
	};
 
	User.getAdminsandGlobalModsandModerators = function (callback) {
		async.parallel([
			async.apply(groups.getMembers, 'administrators', 0, -1),
			async.apply(groups.getMembers, 'Global Moderators', 0, -1),
			async.apply(User.getModeratorUids)
		], function (err, results) {
			if (err) {
				return callback(err);
			}
 
			User.getUsersData(_.union.apply(_, results), callback);
		});
	};
 
	User.getModeratorUids = function (callback) {
		async.waterfall([
			async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
			function (cids, next) {
				var groupNames = cids.map(function (cid) {
					return 'cid:' + cid + ':privileges:mods';
				});
 
				groups.getMembersOfGroups(groupNames, function (err, memberSets) {
					if (err) {
						return next(err);
					}
 
					next(null, _.union.apply(_, memberSets));
				});
			}
		], callback);
	};
 
	User.getModeratedCids = function (uid, callback) {
		var cids;
		async.waterfall([
			function (next) {
				db.getSortedSetRange('categories:cid', 0, -1, next);
			},
			function (_cids, next) {
				cids = _cids;
				User.isModerator(uid, cids, next);
			},
			function (isMods, next) {
				cids = cids.filter(function (cid, index) {
					return cid && isMods[index];
				});
				next(null, cids);
			}
		], callback);
	};
 
	User.addInterstitials = function (callback) {
		plugins.registerHook('core', {
			hook: 'filter:register.interstitial',
			method: function (data, callback) {
				if (meta.config.termsOfUse && !data.userData.acceptTos) {
					data.interstitials.push({
						template: 'partials/acceptTos',
						data: {
							termsOfUse: meta.config.termsOfUse
						},
						callback: function (userData, formData, next) {
							if (formData['agree-terms'] === 'on') {
								userData.acceptTos = true;
							}
 
							next(userData.acceptTos ? null : new Error('[[register:terms_of_use_error]]'));
						}
					});
				}
 
				callback(null, data);
			}
		});
 
		callback();
	};
 
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/webserver.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/webserver.js

Statements: 13.64% (21 / 154)      Branches: 0% (0 / 74)      Functions: 0% (0 / 23)      Lines: 13.64% (21 / 154)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289      2 2 2 2 2 2 2 2 2 2 2 2 2 2 2   2                                                                                                                         1                                                                         1                                                                                             1             1                                         1                                                                                                                                                                                                
 
'use strict';
 
var fs = require('fs');
var path = require('path');
var nconf = require('nconf');
var express = require('express');
var app = express();
var server;
var winston = require('winston');
var async = require('async');
var flash = require('connect-flash');
var compression = require('compression');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var useragent = require('express-useragent');
var favicon = require('serve-favicon');
 
var db = require('./database');
var file = require('./file');
var emailer = require('./emailer');
var meta = require('./meta');
var languages = require('./languages');
var logger = require('./logger');
var plugins = require('./plugins');
var routes = require('./routes');
var auth = require('./routes/authentication');
var templates = require('templates.js');
 
var helpers = require('../public/src/modules/helpers');
 
if (nconf.get('ssl')) {
	server = require('https').createServer({
		key: fs.readFileSync(nconf.get('ssl').key),
		cert: fs.readFileSync(nconf.get('ssl').cert)
	}, app);
} else {
	server = require('http').createServer(app);
}
 
module.exports.server = server;
 
server.on('error', function (err) {
	winston.error(err);
	if (err.code === 'EADDRINUSE') {
		winston.error('NodeBB address in use, exiting...');
		process.exit(0);
	} else {
		throw err;
	}
});
 
module.exports.listen = function (callback) {
	callback = callback || function () {};
	emailer.registerApp(app);
 
	setupExpressApp(app);
 
	helpers.register();
 
	logger.init(app);
 
	initializeNodeBB(function (err) {
		if (err) {
			return callback(err);
		}
 
		winston.info('NodeBB Ready');
 
		require('./socket.io').server.emit('event:nodebb.ready', {
			'cache-buster': meta.config['cache-buster']
		});
 
		plugins.fireHook('action:nodebb.ready');
 
		listen(callback);
	});
};
 
function initializeNodeBB(callback) {
	winston.info('initializing NodeBB ...');
	var middleware = require('./middleware');
 
	async.waterfall([
		async.apply(meta.themes.setupPaths),
		function (next) {
			plugins.init(app, middleware, next);
		},
		async.apply(plugins.fireHook, 'static:assets.prepare', {}),
		async.apply(meta.js.bridgeModules, app),
		function (next) {
			plugins.fireHook('static:app.preload', {
				app: app,
				middleware: middleware
			}, next);
		},
		function (next) {
			plugins.fireHook('filter:hotswap.prepare', [], next);
		},
		function (hotswapIds, next) {
			routes(app, middleware, hotswapIds);
			next();
		},
		function (next) {
			async.series([
				async.apply(meta.js.getFromFile, 'nodebb.min.js'),
				async.apply(meta.js.getFromFile, 'acp.min.js'),
				async.apply(meta.css.getFromFile),
				async.apply(meta.sounds.init),
				async.apply(languages.init),
				async.apply(meta.blacklist.load)
			], next);
		}
	], callback);
}
 
function setupExpressApp(app) {
	var middleware = require('./middleware');
 
	var relativePath = nconf.get('relative_path');
 
	app.engine('tpl', templates.__express);
	app.set('view engine', 'tpl');
	app.set('views', nconf.get('views_dir'));
	app.set('json spaces', process.env.NODE_ENV === 'development' ? 4 : 0);
	app.use(flash());
 
	app.enable('view cache');
 
	if (global.env !== 'development') {
		app.enable('cache');
		app.enable('minification');
	}
 
	app.use(compression());
 
	setupFavicon(app);
 
	app.use(relativePath + '/apple-touch-icon', middleware.routeTouchIcon);
 
	app.use(bodyParser.urlencoded({extended: true}));
	app.use(bodyParser.json());
	app.use(cookieParser());
	app.use(useragent.express());
 
	app.use(session({
		store: db.sessionStore,
		secret: nconf.get('secret'),
		key: nconf.get('sessionKey'),
		cookie: setupCookie(),
		resave: true,
		saveUninitialized: true
	}));
 
	app.use(middleware.addHeaders);
	app.use(middleware.processRender);
	auth.initialize(app, middleware);
 
	var toobusy = require('toobusy-js');
	toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 100);
	toobusy.interval(parseInt(meta.config.eventLoopInterval, 10) || 500);
}
 
function setupFavicon(app) {
	var faviconPath = path.join(nconf.get('base_dir'), 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico');
	if (file.existsSync(faviconPath)) {
		app.use(nconf.get('relative_path'), favicon(faviconPath));
	}
}
 
function setupCookie() {
	var cookie = {
		maxAge: 1000 * 60 * 60 * 24 * (parseInt(meta.config.loginDays, 10) || 14)
	};
 
	if (nconf.get('cookieDomain') || meta.config.cookieDomain) {
		cookie.domain = nconf.get('cookieDomain') || meta.config.cookieDomain;
	}
 
	if (nconf.get('secure')) {
		cookie.secure = true;
	}
 
	var relativePath = nconf.get('relative_path');
	if (relativePath !== '') {
		cookie.path = relativePath;
	}
 
	return cookie;
}
 
function listen(callback) {
	callback = callback || function () {};
	var port = parseInt(nconf.get('port'), 10);
	var isSocket = isNaN(port);
	var socketPath = isSocket ? nconf.get('port') : '';
 
	if (Array.isArray(port)) {
		if (!port.length) {
			winston.error('[startup] empty ports array in config.json');
			process.exit();
		}
 
		winston.warn('[startup] If you want to start nodebb on multiple ports please use loader.js');
		winston.warn('[startup] Defaulting to first port in array, ' + port[0]);
		port = port[0];
		if (!port) {
			winston.error('[startup] Invalid port, exiting');
			process.exit();
		}
	}
 
	if ((port !== 80 && port !== 443) || nconf.get('trust_proxy') === true) {
		winston.info('Enabling \'trust proxy\'');
		app.enable('trust proxy');
	}
 
	if ((port === 80 || port === 443) && process.env.NODE_ENV !== 'development') {
		winston.info('Using ports 80 and 443 is not recommend; use a proxy instead. See README.md');
	}
 
	var bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address'));
	var args = isSocket ? [socketPath] : [port, bind_address];
	var oldUmask;
 
	args.push(function (err) {
		if (err) {
			winston.info('[startup] NodeBB was unable to listen on: ' + bind_address + ':' + port);
			process.exit();
		}
 
		winston.info('NodeBB is now listening on: ' + (isSocket ? socketPath : bind_address + ':' + port));
		if (oldUmask) {
			process.umask(oldUmask);
		}
		callback();
	});
 
	// Alter umask if necessary
	if (isSocket) {
		oldUmask = process.umask('0000');
		module.exports.testSocket(socketPath, function (err) {
			if (!err) {
				server.listen.apply(server, args);
			} else {
				winston.error('[startup] NodeBB was unable to secure domain socket access (' + socketPath + ')');
				winston.error('[startup] ' + err.message);
				process.exit();
			}
		});
	} else {
		server.listen.apply(server, args);
	}
}
 
module.exports.testSocket = function (socketPath, callback) {
	if (typeof socketPath !== 'string') {
		return callback(new Error('invalid socket path : ' + socketPath));
	}
	var net = require('net');
	var file = require('./file');
	async.series([
		function (next) {
			file.exists(socketPath, function (exists) {
				if (exists) {
					next();
				} else {
					callback();
				}
			});
		},
		function (next) {
			var testSocket = new net.Socket();
			testSocket.on('error', function (err) {
				next(err.code !== 'ECONNREFUSED' ? err : null);
			});
			testSocket.connect({ path: socketPath }, function () {
				// Something's listening here, abort
				callback(new Error('port-in-use'));
			});
		},
		async.apply(fs.unlink, socketPath),	// The socket was stale, kick it out of the way
	], callback);
};
 
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/

Statements: 7.3% (34 / 466)      Branches: 0% (0 / 209)      Functions: 0% (0 / 192)      Lines: 7.3% (34 / 466)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/categories/
File Statements Branches Functions Lines
activeusers.js 16.67% (2 / 12) 0% (0 / 2) 0% (0 / 7) 16.67% (2 / 12)
create.js 4.05% (3 / 74) 0% (0 / 42) 0% (0 / 23) 4.05% (3 / 74)
data.js 8.33% (5 / 60) 0% (0 / 39) 0% (0 / 17) 8.33% (5 / 60)
delete.js 10% (4 / 40) 0% (0 / 2) 0% (0 / 28) 10% (4 / 40)
recentreplies.js 8.4% (10 / 119) 0% (0 / 51) 0% (0 / 53) 8.4% (10 / 119)
topics.js 3.03% (2 / 66) 0% (0 / 37) 0% (0 / 23) 3.03% (2 / 66)
unread.js 6.9% (2 / 29) 0% (0 / 14) 0% (0 / 10) 6.9% (2 / 29)
update.js 9.09% (6 / 66) 0% (0 / 22) 0% (0 / 31) 9.09% (6 / 66)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/activeusers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/activeusers.js

Statements: 16.67% (2 / 12)      Branches: 0% (0 / 2)      Functions: 0% (0 / 7)      Lines: 16.67% (2 / 12)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30    2 2                                                    
'use strict';
 
var async = require('async');
var posts = require('../posts');
var db = require('../database');
 
module.exports = function (Categories) {
 
	Categories.getActiveUsers = function (cid, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange('cid:' + cid + ':pids', 0, 24, next);
			},
			function (pids, next) {
				posts.getPostsFields(pids, ['uid'], next);
			},
			function (posts, next) {
				var uids = posts.map(function (post) {
					return post.uid;
				}).filter(function (uid, index, array) {
					return parseInt(uid, 10) && array.indexOf(uid) === index;
				});
 
				next(null, uids);
			}
		], callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/create.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/create.js

Statements: 4.05% (3 / 74)      Branches: 0% (0 / 42)      Functions: 0% (0 / 23)      Lines: 4.05% (3 / 74)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174    2   2                                                                                                                                                                                                                                                                                           1                                                      
'use strict';
 
var async = require('async');
 
var db = require('../database');
var groups = require('../groups');
var plugins = require('../plugins');
var privileges = require('../privileges');
var utils = require('../../public/src/utils');
 
module.exports = function (Categories) {
 
	Categories.create = function (data, callback) {
		var category;
		var parentCid = data.parentCid ? data.parentCid : 0;
 
		async.waterfall([
			function (next) {
				db.incrObjectField('global', 'nextCid', next);
			},
			function (cid, next) {
				data.name = data.name || 'Category ' + cid;
				var slug = cid + '/' + utils.slugify(data.name);
				var order = data.order || cid;	// If no order provided, place it at the end
				var colours = Categories.assignColours();
 
				category = {
					cid: cid,
					name: data.name,
					description: data.description ? data.description : '',
					descriptionParsed: data.descriptionParsed ? data.descriptionParsed : '',
					icon: data.icon ? data.icon : '',
					bgColor: data.bgColor || colours[0],
					color: data.color || colours[1],
					slug: slug,
					parentCid: parentCid,
					topic_count: 0,
					post_count: 0,
					disabled: 0,
					order: order,
					link: '',
					numRecentReplies: 1,
					class: ( data.class ? data.class : 'col-md-3 col-xs-6' ),
					imageClass: 'cover'
				};
 
				plugins.fireHook('filter:category.create', {category: category, data: data}, next);
			},
			function (data, next) {
				category = data.category;
 
				var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'posts:edit', 'posts:delete', 'topics:delete', 'upload:post:image'];
 
				async.series([
					async.apply(db.setObject, 'category:' + category.cid, category),
					function (next) {
						if (category.descriptionParsed) {
							return next();
						}
						Categories.parseDescription(category.cid, category.description, next);
					},
					async.apply(db.sortedSetAdd, 'categories:cid', category.order, category.cid),
					async.apply(db.sortedSetAdd, 'cid:' + parentCid + ':children', category.order, category.cid),
					async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'administrators'),
					async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'registered-users'),
					async.apply(privileges.categories.give, ['find', 'read', 'topics:read'], category.cid, 'guests')
				], next);
			},
			function (results, next) {
				if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) {
					return Categories.copySettingsFrom(data.cloneFromCid, category.cid, !data.parentCid, next);
				}
				next(null, category);
			},
			function (category, next) {
				plugins.fireHook('action:category.create', category);
				next(null, category);
			}
		], callback);
	};
 
	Categories.assignColours = function () {
		var backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'];
		var text = ['#fff', '#fff', '#333', '#fff', '#333', '#fff', '#fff', '#fff'];
		var index = Math.floor(Math.random() * backgrounds.length);
 
		return [backgrounds[index], text[index]];
	};
 
	Categories.copySettingsFrom = function (fromCid, toCid, copyParent, callback) {
		var destination;
		async.waterfall([
			function (next) {
				async.parallel({
					source: async.apply(db.getObject, 'category:' + fromCid),
					destination: async.apply(db.getObject, 'category:' + toCid)
				}, next);
			},
			function (results, next) {
				if (!results.source) {
					return next(new Error('[[error:invalid-cid]]'));
				}
				destination = results.destination;
 
				var tasks = [];
 
				if (copyParent && utils.isNumber(destination.parentCid)) {
					tasks.push(async.apply(db.sortedSetRemove, 'cid:' + destination.parentCid + ':children', toCid));
				}
 
				if (copyParent && utils.isNumber(results.source.parentCid)) {
					tasks.push(async.apply(db.sortedSetAdd, 'cid:' + results.source.parentCid + ':children', results.source.order, toCid));
				}
 
				destination.description = results.source.description;
				destination.descriptionParsed = results.source.descriptionParsed;
				destination.icon = results.source.icon;
				destination.bgColor = results.source.bgColor;
				destination.color = results.source.color;
				destination.link = results.source.link;
				destination.numRecentReplies = results.source.numRecentReplies;
				destination.class = results.source.class;
				destination.imageClass = results.source.imageClass;
 
				if (copyParent) {
					destination.parentCid = results.source.parentCid || 0;
				}
 
				tasks.push(async.apply(db.setObject, 'category:' + toCid, destination));
 
				async.series(tasks, next);
			},
			function (results, next) {
				Categories.copyPrivilegesFrom(fromCid, toCid, next);
			}
		], function (err) {
			callback(err, destination);
		});
	};
 
	Categories.copyPrivilegesFrom = function (fromCid, toCid, callback) {
		async.each(privileges.privilegeList, function (privilege, next) {
			copyPrivilege(privilege, fromCid, toCid, next);
		}, callback);
	};
 
	function copyPrivilege(privilege, fromCid, toCid, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('group:cid:' + toCid + ':privileges:' + privilege + ':members', 0, -1, next);
			},
			function (currentMembers, next) {
				async.eachSeries(currentMembers, function (member, next) {
					groups.leave('cid:' + toCid + ':privileges:' + privilege, member, next);
				}, next);
			},
			function (next) {
				db.getSortedSetRange('group:cid:' + fromCid + ':privileges:' + privilege + ':members', 0, -1, next);
			},
			function (members, next) {
				if (!members || !members.length) {
					return callback();
				}
 
				async.eachSeries(members, function (member, next) {
					groups.join('cid:' + toCid + ':privileges:' + privilege, member, next);
				}, next);
			}
		], callback);
	}
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/data.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/data.js

Statements: 8.33% (5 / 60)      Branches: 0% (0 / 39)      Functions: 0% (0 / 17)      Lines: 8.33% (5 / 60)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116    2 2 2   2                                                                 1                                                                                                                                                        
'use strict';
 
var async = require('async');
var validator = require('validator');
var winston = require('winston');
 
var db = require('../database');
 
module.exports = function (Categories) {
 
	Categories.getCategoryData = function (cid, callback) {
		db.getObject('category:' + cid, function (err, category) {
			if (err) {
				return callback(err);
			}
 
			modifyCategory(category);
			callback(null, category);
		});
	};
 
	Categories.getCategoriesData = function (cids, callback) {
		if (!Array.isArray(cids) || !cids.length) {
			return callback(null, []);
		}
		var keys = cids.map(function (cid) {
			return 'category:' + cid;
		});
 
		db.getObjects(keys, function (err, categories) {
			if (err || !Array.isArray(categories) || !categories.length) {
				return callback(err, []);
			}
 
			categories.forEach(modifyCategory);
			callback(null, categories);
		});
	};
 
	function modifyCategory(category) {
		if (!category) {
			return;
		}
 
		category.name = validator.escape(String(category.name || ''));
		category.disabled = category.hasOwnProperty('disabled') ? parseInt(category.disabled, 10) === 1 : undefined;
		category.icon = category.icon || 'hidden';
		if (category.hasOwnProperty('post_count')) {
			category.post_count = category.totalPostCount = category.post_count || 0;
		}
 
		if (category.hasOwnProperty('topic_count')) {
			category.topic_count = category.totalTopicCount = category.topic_count || 0;
		}
 
		if (category.image) {
			category.backgroundImage = category.image;
		}
 
		if (category.description) {
			category.description = validator.escape(String(category.description));
			category.descriptionParsed = category.descriptionParsed || category.description;
		}
	}
 
	Categories.getCategoryField = function (cid, field, callback) {
		db.getObjectField('category:' + cid, field, callback);
	};
 
	Categories.getCategoriesFields = function (cids, fields, callback) {
		if (!Array.isArray(cids) || !cids.length) {
			return callback(null, []);
		}
 
		var keys = cids.map(function (cid) {
			return 'category:' + cid;
		});
 
		db.getObjectsFields(keys, fields, function (err, categories) {
			if (err) {
				return callback(err);
			}
 
			categories.forEach(modifyCategory);
			callback(null, categories);
		});
	};
 
	Categories.getMultipleCategoryFields = function (cids, fields, callback) {
		winston.warn('[deprecated] Categories.getMultipleCategoryFields is deprecated please use Categories.getCategoriesFields');
		Categories.getCategoriesFields(cids, fields, callback);
	};
 
	Categories.getAllCategoryFields = function (fields, callback) {
		async.waterfall([
			async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
			function (cids, next) {
				Categories.getCategoriesFields(cids, fields, next);
			}
		], callback);
	};
 
	Categories.getCategoryFields = function (cid, fields, callback) {
		db.getObjectFields('category:' + cid, fields, callback);
	};
 
	Categories.setCategoryField = function (cid, field, value, callback) {
		db.setObjectField('category:' + cid, field, value, callback);
	};
 
	Categories.incrementCategoryFieldBy = function (cid, field, value, callback) {
		db.incrObjectFieldBy('category:' + cid, field, value, callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/delete.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/delete.js

Statements: 10% (4 / 40)      Branches: 0% (0 / 2)      Functions: 0% (0 / 28)      Lines: 10% (4 / 40)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107    2 2                                                                       1                                                           1                                                                          
'use strict';
 
var async = require('async');
var db = require('../database');
var batch = require('../batch');
var plugins = require('../plugins');
var topics = require('../topics');
var groups = require('../groups');
var privileges = require('../privileges');
 
module.exports = function (Categories) {
 
	Categories.purge = function (cid, uid, callback) {
		async.waterfall([
			function (next) {
				batch.processSortedSet('cid:' + cid + ':tids', function (tids, next) {
					async.eachLimit(tids, 10, function (tid, next) {
						topics.purgePostsAndTopic(tid, uid, next);
					}, next);
				}, {alwaysStartAt: 0}, next);
			},
			function (next) {
				Categories.getPinnedTids('cid:' + cid + ':tids:pinned', 0, -1, next);
			},
			function (pinnedTids, next) {
				async.eachLimit(pinnedTids, 10, function (tid, next) {
					topics.purgePostsAndTopic(tid, uid, next);
				}, next);
			},
			function (next) {
				purgeCategory(cid, next);
			},
			function (next) {
				plugins.fireHook('action:category.delete', cid);
				next();
			}
		], callback);
	};
 
	function purgeCategory(cid, callback) {
		async.series([
			function (next) {
				db.sortedSetRemove('categories:cid', cid, next);
			},
			function (next) {
				removeFromParent(cid, next);
			},
			function (next) {
				db.deleteAll([
					'cid:' + cid + ':tids',
					'cid:' + cid + ':tids:pinned',
					'cid:' + cid + ':tids:posts',
					'cid:' + cid + ':pids',
					'cid:' + cid + ':read_by_uid',
					'cid:' + cid + ':ignorers',
					'cid:' + cid + ':children',
					'category:' + cid
				], next);
			},
			function (next) {
				async.each(privileges.privilegeList, function (privilege, next) {
					groups.destroy('cid:' + cid + ':privileges:' + privilege, next);
				}, next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	function removeFromParent(cid, callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					parentCid: function (next) {
						Categories.getCategoryField(cid, 'parentCid', next);
					},
					children: function (next) {
						db.getSortedSetRange('cid:' + cid + ':children', 0, -1, next);
					}
				}, next);
			},
			function (results, next) {
				async.parallel([
					function (next) {
						results.parentCid = parseInt(results.parentCid, 10) || 0;
						db.sortedSetRemove('cid:' + results.parentCid + ':children', cid, next);
					},
					function (next) {
						async.each(results.children, function (cid, next) {
							async.parallel([
								function (next) {
									db.setObjectField('category:' + cid, 'parentCid', 0, next);
								},
								function (next) {
									db.sortedSetAdd('cid:0:children', cid, cid, next);
								}
							], next);
						}, next);
					}
				], next);
			}
		], function (err) {
			callback(err);
		});
	}
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/recentreplies.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/recentreplies.js

Statements: 8.4% (10 / 119)      Branches: 0% (0 / 51)      Functions: 0% (0 / 53)      Lines: 8.4% (10 / 119)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253      2 2 2 2   2                                                                                                                                                                                         1                                                                                           1                     1                                 1                                                                                                     1                                                    
 
'use strict';
 
var async = require('async');
var winston = require('winston');
var validator = require('validator');
var _ = require('underscore');
 
var db = require('../database');
var posts = require('../posts');
var topics = require('../topics');
var privileges = require('../privileges');
var batch = require('../batch');
 
 
module.exports = function (Categories) {
 
	Categories.getRecentReplies = function (cid, uid, count, callback) {
		if (!parseInt(count, 10)) {
			return callback(null, []);
		}
 
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange('cid:' + cid + ':pids', 0, count - 1, next);
			},
			function (pids, next) {
				privileges.posts.filter('read', pids, uid, next);
			},
			function (pids, next) {
				posts.getPostSummaryByPids(pids, uid, {stripTags: true}, next);
			}
		], callback);
	};
 
	Categories.updateRecentTid = function (cid, tid, callback) {
		async.parallel({
			count: function (next) {
				db.sortedSetCard('cid:' + cid + ':recent_tids', next);
			},
			numRecentReplies: function (next) {
				db.getObjectField('category:' + cid, 'numRecentReplies', next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (results.count < results.numRecentReplies) {
				return db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid, callback);
			}
			async.waterfall([
				function (next) {
					db.getSortedSetRangeWithScores('cid:' + cid + ':recent_tids', 0, results.count - results.numRecentReplies, next);
				},
				function (data, next) {
					if (!data.length) {
						return next();
					}
					db.sortedSetsRemoveRangeByScore(['cid:' + cid + ':recent_tids'], '-inf', data[data.length - 1].score, next);
				},
				function (next) {
					db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid, next);
				}
			], callback);
		});
	};
 
	Categories.getRecentTopicReplies = function (categoryData, uid, callback) {
		if (!Array.isArray(categoryData) || !categoryData.length) {
			return callback();
		}
 
		async.waterfall([
			function (next) {
				var keys = categoryData.map(function (category) {
					return 'cid:' + category.cid + ':recent_tids';
				});
				db.getSortedSetsMembers(keys, next);
			},
			function (results, next) {
				var tids = _.flatten(results);
 
				tids = tids.filter(function (tid, index, array) {
					return !!tid && array.indexOf(tid) === index;
				});
				privileges.topics.filterTids('read', tids, uid, next);
			},
			function (tids, next) {
				getTopics(tids, next);
			},
			function (topics, next) {
				assignTopicsToCategories(categoryData, topics);
 
				bubbleUpChildrenPosts(categoryData);
 
				next();
			}
		], callback);
	};
 
	function getTopics(tids, callback) {
		var topicData;
		async.waterfall([
			function (next) {
				topics.getTopicsFields(tids, ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'], next);
			},
			function (_topicData, next) {
				topicData = _topicData;
				topicData.forEach(function (topic) {
					if (topic) {
						topic.teaserPid = topic.teaserPid || topic.mainPid;
					}
				});
				var cids = _topicData.map(function (topic) {
					return topic && topic.cid;
				}).filter(function (cid, index, array) {
					return cid && array.indexOf(cid) === index;
				});
 
				async.parallel({
					categoryData: async.apply(Categories.getCategoriesFields, cids, ['cid', 'parentCid']),
					teasers: async.apply(topics.getTeasers, _topicData),
				}, next);
			},
			function (results, next) {
				var parentCids = {};
				results.categoryData.forEach(function (category) {
					parentCids[category.cid] = category.parentCid;
				});
				results.teasers.forEach(function (teaser, index) {
					if (teaser) {
						teaser.cid = topicData[index].cid;
						teaser.parentCid = parseInt(parentCids[teaser.cid]) || 0;
						teaser.tid = teaser.uid = teaser.user.uid = undefined;
						teaser.topic = {
							slug: topicData[index].slug,
							title: validator.escape(String(topicData[index].title))
						};
					}
				});
				results.teasers = results.teasers.filter(Boolean);
				next(null, results.teasers);
			}
		], callback);
	}
 
	function assignTopicsToCategories(categories, topics) {
		categories.forEach(function (category) {
			category.posts = topics.filter(function (topic) {
				return topic.cid && (parseInt(topic.cid, 10) === parseInt(category.cid, 10) ||
					parseInt(topic.parentCid, 10) === parseInt(category.cid, 10));
			}).sort(function (a, b) {
				return b.pid - a.pid;
			}).slice(0, parseInt(category.numRecentReplies, 10));
		});
	}
 
	function bubbleUpChildrenPosts(categoryData) {
		categoryData.forEach(function (category) {
			if (category.posts.length) {
				return;
			}
			var posts = [];
			getPostsRecursive(category, posts);
 
			posts.sort(function (a, b) {
				return b.pid - a.pid;
			});
			if (posts.length) {
				category.posts = [posts[0]];
			}
		});
	}
 
	function getPostsRecursive(category, posts) {
		category.posts.forEach(function (p) {
			posts.push(p);
		});
 
		category.children.forEach(function (child) {
			getPostsRecursive(child, posts);
		});
	}
 
	Categories.moveRecentReplies = function (tid, oldCid, cid, callback) {
		callback = callback || function () {};
		updatePostCount(tid, oldCid, cid);
		topics.getPids(tid, function (err, pids) {
			if (err) {
				return winston.error(err.message);
			}
 
			if (!Array.isArray(pids) || !pids.length) {
				return;
			}
 
			batch.processArray(pids, function (pids, next) {
				async.waterfall([
					function (next) {
						posts.getPostsFields(pids, ['timestamp'], next);
					},
					function (postData, next) {
						var timestamps = postData.map(function (post) {
							return post && post.timestamp;
						});
 
						async.parallel([
							function (next) {
								db.sortedSetRemove('cid:' + oldCid + ':pids', pids, next);
							},
							function (next) {
								db.sortedSetAdd('cid:' + cid + ':pids', timestamps, pids, next);
							}
						], next);
					}
				], next);
			}, function (err) {
				if (err) {
					winston.error(err.stack);
				}
				callback(err);
			});
		});
	};
 
	function updatePostCount(tid, oldCid, newCid) {
		topics.getTopicField(tid, 'postcount', function (err, postCount) {
			if (err) {
				return winston.error(err.message);
			}
			if (!parseInt(postCount, 10)) {
				return;
			}
			async.parallel([
				function (next) {
					db.incrObjectFieldBy('category:' + oldCid, 'post_count', -postCount, next);
				},
				function (next) {
					db.incrObjectFieldBy('category:' + newCid, 'post_count', postCount, next);
				}
			], function (err) {
				if (err) {
					winston.error(err.message);
				}
			});
		});
	}
};
 
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/topics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/topics.js

Statements: 3.03% (2 / 66)      Branches: 0% (0 / 37)      Functions: 0% (0 / 23)      Lines: 3.03% (2 / 66)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142    2   2                                                                                                                                                                                                                                                                                  
'use strict';
 
var async = require('async');
 
var db = require('../database');
var topics = require('../topics');
var plugins = require('../plugins');
 
module.exports = function (Categories) {
 
	Categories.getCategoryTopics = function (data, callback) {
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:category.topics.prepare', data, next);
			},
			function (data, next) {
				Categories.getTopicIds(data.cid, data.set, data.reverse, data.start, data.stop, next);
			},
			function (tids, next) {
				topics.getTopicsByTids(tids, data.uid, next);
			},
			function (topics, next) {
				if (!Array.isArray(topics) || !topics.length) {
					return next(null, {topics: [], uid: data.uid});
				}
 
				for (var i = 0; i < topics.length; ++i) {
					topics[i].index = data.start + i;
				}
 
				plugins.fireHook('filter:category.topics.get', {topics: topics, uid: data.uid}, next);
			},
			function (results, next) {
				next(null, {topics: results.topics, nextStart: data.stop + 1});
			}
		], callback);
	};
 
	Categories.getTopicIds = function (cid, set, reverse, start, stop, callback) {
	 	var pinnedTids;
		var pinnedCount;
		var totalPinnedCount;
 
		async.waterfall([
			function (next) {
				Categories.getPinnedTids(cid, 0, -1, next);
			},
			function (_pinnedTids, next) {
				totalPinnedCount = _pinnedTids.length;
 
				pinnedTids = _pinnedTids.slice(start, stop === -1 ? undefined : stop + 1);
 
				pinnedCount = pinnedTids.length;
 
				var topicsPerPage = stop - start + 1;
 
				var normalTidsToGet = Math.max(0, topicsPerPage - pinnedCount);
 
				if (!normalTidsToGet && stop !== -1) {
					return next(null, []);
				}
				if (start > 0 && totalPinnedCount) {
					start -= totalPinnedCount - pinnedCount;
				}
				stop = stop === -1 ? stop : start + normalTidsToGet - 1;
 
				if (Array.isArray(set)) {
					db[reverse ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({sets: set, start: start, stop: stop}, next);
				} else {
					db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop, next);
				}
			},
			function (normalTids, next) {
				normalTids = normalTids.filter(function (tid) {
					return pinnedTids.indexOf(tid) === -1;
				});
 
				next(null, pinnedTids.concat(normalTids));
			}
		], callback);
	};
 
	Categories.getAllTopicIds = function (cid, start, stop, callback) {
		db.getSortedSetRange(['cid:' + cid + ':tids:pinned', 'cid:' + cid + ':tids'], start, stop, callback);
	};
 
	Categories.getPinnedTids = function (cid, start, stop, callback) {
		db.getSortedSetRevRange('cid:' + cid + ':tids:pinned', start, stop, callback);
	};
 
	Categories.modifyTopicsByPrivilege = function (topics, privileges) {
		if (!Array.isArray(topics) || !topics.length || privileges.isAdminOrMod) {
			return;
		}
 
		topics.forEach(function (topic) {
			if (topic.deleted && !topic.isOwner) {
				topic.title = '[[topic:topic_is_deleted]]';
				topic.slug = topic.tid;
				topic.teaser = null;
				topic.noAnchor = true;
				topic.tags = [];
			}
		});
	};
 
	Categories.getTopicIndex = function (tid, callback) {
		console.warn('[Categories.getTopicIndex] deprecated');
		callback(null, 1);
	};
 
	Categories.onNewPostMade = function (cid, pinned, postData, callback) {
		if (!cid || !postData) {
			return callback();
		}
 
		async.parallel([
			function (next) {
				db.sortedSetAdd('cid:' + cid + ':pids', postData.timestamp, postData.pid, next);
			},
			function (next) {
				db.incrObjectField('category:' + cid, 'post_count', next);
			},
			function (next) {
				if (parseInt(pinned, 10) === 1) {
					next();
				} else {
					db.sortedSetAdd('cid:' + cid + ':tids', postData.timestamp, postData.tid, next);
				}
			},
			function (next) {
				Categories.updateRecentTid(cid, postData.tid, next);
			},
			function (next) {
				db.sortedSetIncrBy('cid:' + cid + ':tids:posts', 1, postData.tid, next);
			}
		], callback);
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/unread.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/unread.js

Statements: 6.9% (2 / 29)      Branches: 0% (0 / 14)      Functions: 0% (0 / 10)      Lines: 6.9% (2 / 29)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58      2 2                                                                                                          
 
"use strict";
 
var async = require('async');
var db = require('../database');
 
module.exports = function (Categories) {
 
	Categories.markAsRead = function (cids, uid, callback) {
		callback = callback || function () {};
		if (!Array.isArray(cids) || !cids.length) {
			return callback();
		}
		var keys = cids.map(function (cid) {
			return 'cid:' + cid + ':read_by_uid';
		});
 
		db.isMemberOfSets(keys, uid, function (err, hasRead) {
			if (err) {
				return callback(err);
			}
 
			keys = keys.filter(function (key, index) {
				return !hasRead[index];
			});
 
			if (!keys.length) {
				return callback();
			}
 
			db.setsAdd(keys, uid, callback);
		});
	};
 
	Categories.markAsUnreadForAll = function (cid, callback) {
		if (!parseInt(cid, 10)) {
			return callback();
		}
		callback = callback || function () {};
		db.delete('cid:' + cid + ':read_by_uid', callback);
	};
 
	Categories.hasReadCategories = function (cids, uid, callback) {
		var sets = [];
 
		for (var i = 0, ii = cids.length; i < ii; i++) {
			sets.push('cid:' + cids[i] + ':read_by_uid');
		}
 
		db.isMemberOfSets(sets, uid, callback);
	};
 
	Categories.hasReadCategory = function (cid, uid, callback) {
		db.isSetMember('cid:' + cid + ':read_by_uid', uid, callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/update.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/categories/update.js

Statements: 9.09% (6 / 66)      Branches: 0% (0 / 22)      Functions: 0% (0 / 31)      Lines: 9.09% (6 / 66)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149      2 2                                   1                                                                                     1                                         1                                                       1                                                                    
 
'use strict';
 
var async = require('async');
var db = require('../database');
var utils = require('../../public/src/utils');
var translator = require('../../public/src/modules/translator');
var plugins = require('../plugins');
 
module.exports = function (Categories) {
 
	Categories.update = function (modified, callback) {
 
		var cids = Object.keys(modified);
 
		async.each(cids, function (cid, next) {
			updateCategory(cid, modified[cid], next);
		}, function (err) {
			callback(err, cids);
		});
	};
 
	function updateCategory(cid, modifiedFields, callback) {
		var category;
		async.waterfall([
			function (next) {
				Categories.exists(cid, next);
			},
			function (exists, next) {
				if (!exists) {
					return callback();
				}
 
				if (modifiedFields.hasOwnProperty('name')) {
					translator.translate(modifiedFields.name, function (translated) {
						modifiedFields.slug = cid + '/' + utils.slugify(translated);
						next();
					});
				} else {
					next();
				}
			},
			function (next) {
				plugins.fireHook('filter:category.update', {category: modifiedFields}, next);
			},
			function (categoryData, next) {
				category = categoryData.category;
				var fields = Object.keys(category);
				// move parent to front, so its updated first
				var parentCidIndex = fields.indexOf('parentCid');
				if (parentCidIndex !== -1 && fields.length > 1) {
					fields.splice(0, 0, fields.splice(parentCidIndex, 1)[0]);
				}
 
				async.eachSeries(fields, function (key, next) {
					updateCategoryField(cid, key, category[key], next);
				}, next);
			},
			function (next) {
				plugins.fireHook('action:category.update', {cid: cid, modified: category});
				next();
			}
		], callback);
	}
 
	function updateCategoryField(cid, key, value, callback) {
		if (key === 'parentCid') {
			return updateParent(cid, value, callback);
		}
 
		async.waterfall([
			function (next) {
				db.setObjectField('category:' + cid, key, value, next);
			},
			function (next) {
				if (key === 'order') {
					updateOrder(cid, value, next);
				} else if (key === 'description') {
					Categories.parseDescription(cid, value, next);
				} else {
					next();
				}
			}
		], callback);
	}
 
	function updateParent(cid, newParent, callback) {
		if (parseInt(cid, 10) === parseInt(newParent, 10)) {
			return callback(new Error('[[error:cant-set-self-as-parent]]'));
		}
		async.waterfall([
			function (next) {
				Categories.getCategoryField(cid, 'parentCid', next);
			},
			function (oldParent, next) {
				async.series([
					function (next) {
						oldParent = parseInt(oldParent, 10) || 0;
						db.sortedSetRemove('cid:' + oldParent + ':children', cid, next);
					},
					function (next) {
						newParent = parseInt(newParent, 10) || 0;
						db.sortedSetAdd('cid:' + newParent + ':children', cid, cid, next);
					},
					function (next) {
						db.setObjectField('category:' + cid, 'parentCid', newParent, next);
					}
				], next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	function updateOrder(cid, order, callback) {
		async.waterfall([
			function (next) {
				Categories.getCategoryField(cid, 'parentCid', next);
			},
			function (parentCid, next) {
				async.parallel([
					function (next) {
						db.sortedSetAdd('categories:cid', order, cid, next);
					},
					function (next) {
						parentCid = parseInt(parentCid, 10) || 0;
						db.sortedSetAdd('cid:' + parentCid + ':children', order, cid, next);
					}
				], next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	Categories.parseDescription = function (cid, description, callback) {
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:parse.raw', description, next);
			},
			function (parsedDescription, next) {
				Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription, next);
			}
		], callback);
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/

Statements: 5.26% (84 / 1596)      Branches: 0% (0 / 1015)      Functions: 0% (0 / 307)      Lines: 5.26% (84 / 1596)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/controllers/
File Statements Branches Functions Lines
accounts.js 50% (1 / 2) 100% (0 / 0) 100% (0 / 0) 50% (1 / 2)
admin.js 50% (1 / 2) 100% (0 / 0) 100% (0 / 0) 50% (1 / 2)
api.js 2.67% (5 / 187) 0% (0 / 151) 0% (0 / 38) 2.67% (5 / 187)
authentication.js 5% (11 / 220) 0% (0 / 131) 0% (0 / 52) 5% (11 / 220)
categories.js 12.9% (4 / 31) 0% (0 / 22) 0% (0 / 5) 12.9% (4 / 31)
category.js 3% (3 / 100) 0% (0 / 62) 0% (0 / 14) 3% (3 / 100)
globalmods.js 11.11% (1 / 9) 0% (0 / 4) 0% (0 / 2) 11.11% (1 / 9)
groups.js 5.13% (4 / 78) 0% (0 / 44) 0% (0 / 22) 5.13% (4 / 78)
helpers.js 7.23% (6 / 83) 0% (0 / 35) 0% (0 / 23) 7.23% (6 / 83)
index.js 2.43% (5 / 206) 0% (0 / 159) 0% (0 / 29) 2.43% (5 / 206)
mods.js 16.67% (2 / 12) 0% (0 / 10) 0% (0 / 2) 16.67% (2 / 12)
popular.js 6.45% (2 / 31) 0% (0 / 24) 0% (0 / 2) 6.45% (2 / 31)
posts.js 8.33% (1 / 12) 0% (0 / 6) 0% (0 / 2) 8.33% (1 / 12)
recent.js 8.51% (4 / 47) 0% (0 / 16) 0% (0 / 7) 8.51% (4 / 47)
search.js 6.25% (2 / 32) 0% (0 / 18) 0% (0 / 2) 6.25% (2 / 32)
sitemap.js 2.56% (1 / 39) 0% (0 / 18) 0% (0 / 9) 2.56% (1 / 39)
tags.js 9.52% (4 / 42) 0% (0 / 10) 0% (0 / 9) 9.52% (4 / 42)
topics.js 2.91% (5 / 172) 0% (0 / 148) 0% (0 / 22) 2.91% (5 / 172)
unread.js 9.62% (5 / 52) 0% (0 / 27) 0% (0 / 9) 9.62% (5 / 52)
uploads.js 11.48% (14 / 122) 0% (0 / 66) 0% (0 / 28) 11.48% (14 / 122)
users.js 2.56% (3 / 117) 0% (0 / 64) 0% (0 / 30) 2.56% (3 / 117)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts.js

Statements: 50% (1 / 2)      Branches: 100% (0 / 0)      Functions: 100% (0 / 0)      Lines: 50% (1 / 2)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18    2                              
'use strict';
 
var accountsController = {
	profile: require('./accounts/profile'),
	edit: require('./accounts/edit'),
	info: require('./accounts/info'),
	settings: require('./accounts/settings'),
	groups: require('./accounts/groups'),
	follow: require('./accounts/follow'),
	posts: require('./accounts/posts'),
	notifications: require('./accounts/notifications'),
	chats: require('./accounts/chats'),
	session: require('./accounts/session')
};
 
module.exports = accountsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin.js

Statements: 50% (1 / 2)      Branches: 100% (0 / 0)      Functions: 100% (0 / 0)      Lines: 50% (1 / 2)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37    2                                                                    
"use strict";
 
var adminController = {
	dashboard: require('./admin/dashboard'),
	categories: require('./admin/categories'),
	tags: require('./admin/tags'),
	flags: require('./admin/flags'),
	blacklist: require('./admin/blacklist'),
	groups: require('./admin/groups'),
	appearance: require('./admin/appearance'),
	extend: {
		widgets: require('./admin/widgets'),
		rewards: require('./admin/rewards')
	},
	events: require('./admin/events'),
	logs: require('./admin/logs'),
	errors: require('./admin/errors'),
	database: require('./admin/database'),
	cache: require('./admin/cache'),
	plugins: require('./admin/plugins'),
	languages: require('./admin/languages'),
	settings: require('./admin/settings'),
	logger: require('./admin/logger'),
	sounds: require('./admin/sounds'),
	homepage: require('./admin/homepage'),
	navigation: require('./admin/navigation'),
	social: require('./admin/social'),
	themes: require('./admin/themes'),
	users: require('./admin/users'),
	uploads: require('./admin/uploads'),
	info: require('./admin/info')
};
 
 
module.exports = adminController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/api.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/api.js

Statements: 2.67% (5 / 187)      Branches: 0% (0 / 151)      Functions: 0% (0 / 38)      Lines: 2.67% (5 / 187)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336    2 2 2   2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       1                                                                                                                                                          
"use strict";
 
var async = require('async');
var validator = require('validator');
var nconf = require('nconf');
 
var meta = require('../meta');
var user = require('../user');
var posts = require('../posts');
var topics = require('../topics');
var categories = require('../categories');
var privileges = require('../privileges');
var plugins = require('../plugins');
var widgets = require('../widgets');
var translator = require('../../public/src/modules/translator');
var accountHelpers = require('../controllers/accounts/helpers');
 
var apiController = {};
 
apiController.getConfig = function (req, res, next) {
	var config = {};
	config.environment = process.env.NODE_ENV;
	config.relative_path = nconf.get('relative_path');
	config.version = nconf.get('version');
	config.siteTitle = validator.escape(String(meta.config.title || meta.config.browserTitle || 'NodeBB'));
	config.browserTitle = validator.escape(String(meta.config.browserTitle || meta.config.title || 'NodeBB'));
	config.titleLayout = (meta.config.titleLayout || '{pageTitle} | {browserTitle}').replace(/{/g, '&#123;').replace(/}/g, '&#125;');
	config.showSiteTitle = parseInt(meta.config.showSiteTitle, 10) === 1;
	config.minimumTitleLength = meta.config.minimumTitleLength;
	config.maximumTitleLength = meta.config.maximumTitleLength;
	config.minimumPostLength = meta.config.minimumPostLength;
	config.maximumPostLength = meta.config.maximumPostLength;
	config.minimumTagsPerTopic = meta.config.minimumTagsPerTopic || 0;
	config.maximumTagsPerTopic = meta.config.maximumTagsPerTopic || 5;
	config.minimumTagLength = meta.config.minimumTagLength || 3;
	config.maximumTagLength = meta.config.maximumTagLength || 15;
	config.hasImageUploadPlugin = plugins.hasListeners('filter:uploadImage');
	config.useOutgoingLinksPage = parseInt(meta.config.useOutgoingLinksPage, 10) === 1;
	config.allowGuestSearching = parseInt(meta.config.allowGuestSearching, 10) === 1;
	config.allowGuestUserSearching = parseInt(meta.config.allowGuestUserSearching, 10) === 1;
	config.allowGuestHandles = parseInt(meta.config.allowGuestHandles, 10) === 1;
	config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1;
	config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1;
	config.usePagination = parseInt(meta.config.usePagination, 10) === 1;
	config.disableChat = parseInt(meta.config.disableChat, 10) === 1;
	config.disableChatMessageEditing = parseInt(meta.config.disableChatMessageEditing, 10) === 1;
	config.socketioTransports = nconf.get('socket.io:transports') || ['polling', 'websocket'];
	config.websocketAddress = nconf.get('socket.io:address') || '';
	config.maxReconnectionAttempts = meta.config.maxReconnectionAttempts || 5;
	config.reconnectionDelay = meta.config.reconnectionDelay || 1500;
	config.topicsPerPage = meta.config.topicsPerPage || 20;
	config.postsPerPage = meta.config.postsPerPage || 20;
	config.maximumFileSize = meta.config.maximumFileSize;
	config['theme:id'] = meta.config['theme:id'];
	config['theme:src'] = meta.config['theme:src'];
	config.defaultLang = meta.config.defaultLang || 'en-GB';
	config.userLang = req.query.lang ? validator.escape(String(req.query.lang)) : config.defaultLang;
	config.loggedIn = !!req.user;
	config['cache-buster'] = meta.config['cache-buster'] || '';
	config.requireEmailConfirmation = parseInt(meta.config.requireEmailConfirmation, 10) === 1;
	config.topicPostSort = meta.config.topicPostSort || 'oldest_to_newest';
	config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
	config.csrf_token = req.csrfToken();
	config.searchEnabled = plugins.hasListeners('filter:search.query');
	config.bootswatchSkin = 'default';
 
	var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff;
	config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff;
 
	config.cookies = {
		enabled: parseInt(meta.config.cookieConsentEnabled, 10) === 1,
		message: translator.escape(meta.config.cookieConsentMessage || '[[global:cookies.message]]').replace(/\\/g, '\\\\'),
		dismiss: translator.escape(meta.config.cookieConsentDismiss || '[[global:cookies.accept]]').replace(/\\/g, '\\\\'),
		link: translator.escape(meta.config.cookieConsentLink || '[[global:cookies.learn_more]]').replace(/\\/g, '\\\\')
	};
 
	async.waterfall([
		function (next) {
			if (!req.user) {
				return next(null, config);
			}
			user.getSettings(req.uid, next);
		},
		function (settings, next) {
			config.usePagination = settings.usePagination;
			config.topicsPerPage = settings.topicsPerPage;
			config.postsPerPage = settings.postsPerPage;
			config.userLang = (req.query.lang ? validator.escape(String(req.query.lang)) : null) || settings.userLang || config.defaultLang;
			config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab;
			config.topicPostSort = settings.topicPostSort || config.topicPostSort;
			config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort;
			config.topicSearchEnabled = settings.topicSearchEnabled || false;
			config.delayImageLoading = settings.delayImageLoading !== undefined ? settings.delayImageLoading : true;
			config.bootswatchSkin = settings.bootswatchSkin || config.bootswatchSkin;
			plugins.fireHook('filter:config.get', config, next);
		}
	], function (err, config) {
		if (err) {
			return next(err);
		}
 
		if (res.locals.isAPI) {
			res.json(config);
		} else {
			next(null, config);
		}
	});
};
 
 
apiController.renderWidgets = function (req, res, next) {
	var areas = {
		template: req.query.template,
		locations: req.query.locations,
		url: req.query.url
	};
 
	if (!areas.template || !areas.locations) {
		return res.status(200).json({});
	}
 
	widgets.render(req.uid,
		{
			template: areas.template,
			url: areas.url,
			locations: areas.locations,
			isMobile: req.query.isMobile === 'true'
		},
		req,
		res,
		function (err, widgets) {
		if (err) {
			return next(err);
		}
		res.status(200).json(widgets);
	});
};
 
apiController.getPostData = function (pid, uid, callback) {
	async.parallel({
		privileges: function (next) {
			privileges.posts.get([pid], uid, next);
		},
		post: function (next) {
			posts.getPostData(pid, next);
		}
	}, function (err, results) {
		if (err || !results.post) {
			return callback(err);
		}
 
		var post = results.post;
		var privileges = results.privileges[0];
 
		if (!privileges.read || !privileges['topics:read']) {
			return callback();
		}
 
		post.ip = privileges.isAdminOrMod ? post.ip : undefined;
		var selfPost = uid && uid === parseInt(post.uid, 10);
		if (post.deleted && !(privileges.isAdminOrMod || selfPost)) {
			post.content = '[[topic:post_is_deleted]]';
		}
		callback(null, post);
	});
};
 
apiController.getTopicData = function (tid, uid, callback) {
	async.parallel({
		privileges: function (next) {
			privileges.topics.get(tid, uid, next);
		},
		topic: function (next) {
			topics.getTopicData(tid, next);
		}
	}, function (err, results) {
		if (err || !results.topic) {
			return callback(err);
		}
 
		if (!results.privileges.read || !results.privileges['topics:read'] || (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted)) {
			return callback();
		}
		callback(null, results.topic);
	});
};
 
apiController.getCategoryData = function (cid, uid, callback) {
	async.parallel({
		privileges: function (next) {
			privileges.categories.get(cid, uid, next);
		},
		category: function (next) {
			categories.getCategoryData(cid, next);
		}
	}, function (err, results) {
		if (err || !results.category) {
			return callback(err);
		}
 
		if (!results.privileges.read) {
			return callback();
		}
		callback(null, results.category);
	});
};
 
 
apiController.getObject = function (req, res, next) {
	var methods = {
		post: apiController.getPostData,
		topic: apiController.getTopicData,
		category: apiController.getCategoryData
	};
	var method = methods[req.params.type];
	if (!method) {
		return next();
	}
	method(req.params.id, req.uid, function (err, result) {
		if (err || !result) {
			return next(err);
		}
 
		res.json(result);
	});
};
 
apiController.getCurrentUser = function (req, res, next) {
	if (!req.uid) {
		return res.status(401).json('not-authorized');
	}
	async.waterfall([
		function (next) {
			user.getUserField(req.uid, 'userslug', next);
		},
		function (userslug, next) {
			accountHelpers.getUserDataByUserSlug(userslug, req.uid, next);
		}
	], function (err, userData) {
		if (err) {
			return next(err);
		}
		res.json(userData);
	});
};
 
apiController.getUserByUID = function (req, res, next) {
	byType('uid', req, res, next);
};
 
apiController.getUserByUsername = function (req, res, next) {
	byType('username', req, res, next);
};
 
apiController.getUserByEmail = function (req, res, next) {
	byType('email', req, res, next);
};
 
function byType(type, req, res, next) {
	apiController.getUserDataByField(req.uid, type, req.params[type], function (err, data) {
		if (err || !data) {
			return next(err);
		}
		res.json(data);
	});
}
 
apiController.getUserDataByField = function (callerUid, field, fieldValue, callback) {
	async.waterfall([
		function (next) {
			if (field === 'uid') {
				next(null, fieldValue);
			} else if (field === 'username') {
				user.getUidByUsername(fieldValue, next);
			} else if (field === 'email') {
				user.getUidByEmail(fieldValue, next);
			} else {
				next();
			}
		},
		function (uid, next) {
			if (!uid) {
				return next();
			}
			apiController.getUserDataByUID(callerUid, uid, next);
		}
	], callback);
};
 
apiController.getUserDataByUID = function (callerUid, uid, callback) {
	if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) {
		return callback(new Error('[[error:no-privileges]]'));
	}
 
	if (!parseInt(uid, 10)) {
		return callback(new Error('[[error:no-user]]'));
	}
 
	async.parallel({
		userData: async.apply(user.getUserData, uid),
		settings: async.apply(user.getSettings, uid)
	}, function (err, results) {
		if (err || !results.userData) {
			return callback(err || new Error('[[error:no-user]]'));
		}
 
		results.userData.email = results.settings.showemail ? results.userData.email : undefined;
		results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined;
 
		callback(null, results.userData);
	});
};
 
apiController.getModerators = function (req, res, next) {
	categories.getModerators(req.params.cid, function (err, moderators) {
		if (err) {
			return next(err);
		}
		res.json({moderators: moderators});
	});
};
 
 
apiController.getRecentPosts = function (req, res, next) {
	posts.getRecentPosts(req.uid, 0, 19, req.params.term, function (err, data) {
		if (err) {
			return next(err);
		}
 
		res.json(data);
	});
};
 
module.exports = apiController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/authentication.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/authentication.js

Statements: 5% (11 / 220)      Branches: 0% (0 / 131)      Functions: 0% (0 / 52)      Lines: 5% (11 / 220)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444    2 2 2 2 2 2 2   2                                                                                                                                                                               1                                                                                   1                                                                                                                                                                           1                                                                                                                                                                                                                                                                                                                                                                                                                                                  
"use strict";
 
var async = require('async');
var winston = require('winston');
var passport = require('passport');
var nconf = require('nconf');
var validator = require('validator');
var _ = require('underscore');
var url = require('url');
 
var db = require('../database');
var meta = require('../meta');
var user = require('../user');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
var Password = require('../password');
 
var sockets = require('../socket.io');
 
var authenticationController = {};
 
authenticationController.register = function (req, res, next) {
	var registrationType = meta.config.registrationType || 'normal';
 
	if (registrationType === 'disabled') {
		return res.sendStatus(403);
	}
 
	var userData = {};
 
	for (var key in req.body) {
		if (req.body.hasOwnProperty(key)) {
			userData[key] = req.body[key];
		}
	}
 
	async.waterfall([
		function (next) {
			if (registrationType === 'invite-only' || registrationType === 'admin-invite-only') {
				user.verifyInvitation(userData, next);
			} else {
				next();
			}
		},
		function (next) {
			if (!userData.email) {
				return next(new Error('[[error:invalid-email]]'));
			}
 
			if (!userData.username || userData.username.length < meta.config.minimumUsernameLength) {
				return next(new Error('[[error:username-too-short]]'));
			}
 
			if (userData.username.length > meta.config.maximumUsernameLength) {
				return next(new Error('[[error:username-too-long'));
			}
 
			user.isPasswordValid(userData.password, next);
		},
		function (next) {
			if (registrationType === 'normal' || registrationType === 'invite-only' || registrationType === 'admin-invite-only') {
				next(null, false);
			} else if (registrationType === 'admin-approval') {
				next(null, true);
			} else if (registrationType === 'admin-approval-ip') {
				db.sortedSetCard('ip:' + req.ip + ':uid', function (err, count) {
					if (err) {
						next(err);
					} else {
						next(null, !!count);
					}
				});
			}
		},
		function (queue, next) {
			res.locals.processLogin = true;	// set it to false in plugin if you wish to just register only
			plugins.fireHook('filter:register.check', {req: req, res: res, userData: userData, queue: queue}, next);
		},
		function (data, next) {
			if (data.queue) {
				addToApprovalQueue(req, userData, next);
			} else {
				registerAndLoginUser(req, res, userData, next);
			}
		}
	], function (err, data) {
		if (err) {
			return res.status(400).send(err.message);
		}
 
		if (req.body.userLang) {
			user.setSetting(data.uid, 'userLang', req.body.userLang);
		}
 
		res.json(data);
	});
};
 
function registerAndLoginUser(req, res, userData, callback) {
	var uid;
	async.waterfall([
		function (next) {
			plugins.fireHook('filter:register.interstitial', {
				userData: userData,
				interstitials: []
			}, function (err, data) {
				if (err) {
					return next(err);
				}
 
				// If interstitials are found, save registration attempt into session and abort
				var deferRegistration = data.interstitials.length;
 
				if (!deferRegistration) {
					return next();
				} else {
					userData.register = true;
					req.session.registration = userData;
					return res.json({ referrer: nconf.get('relative_path') + '/register/complete' });
				}
			});
		},
		function (next) {
			user.create(userData, next);
		},
		function (_uid, next) {
			uid = _uid;
			if (res.locals.processLogin) {
				authenticationController.doLogin(req, uid, next);
			} else {
				next();
			}
		},
		function (next) {
			user.deleteInvitationKey(userData.email);
			plugins.fireHook('filter:register.complete', {uid: uid, referrer: req.body.referrer || nconf.get('relative_path') + '/'}, next);
		}
	], callback);
}
 
function addToApprovalQueue(req, userData, callback) {
	async.waterfall([
		function (next) {
			userData.ip = req.ip;
			user.addToApprovalQueue(userData, next);
		},
		function (next) {
			next(null, {message: '[[register:registration-added-to-queue]]'});
		}
	], callback);
}
 
authenticationController.registerComplete = function (req, res, next) {
	// For the interstitials that respond, execute the callback with the form body
	plugins.fireHook('filter:register.interstitial', {
		userData: req.session.registration,
		interstitials: []
	}, function (err, data) {
		if (err) {
			return next(err);
		}
 
		var callbacks = data.interstitials.reduce(function (memo, cur) {
			if (cur.hasOwnProperty('callback') && typeof cur.callback === 'function') {
				memo.push(async.apply(cur.callback, req.session.registration, req.body));
			}
 
			return memo;
		}, []);
 
		var done = function () {
			delete req.session.registration;
 
			if (req.session.returnTo) {
				res.redirect(req.session.returnTo);
			} else {
				res.redirect(nconf.get('relative_path') + '/');
			}
		};
 
		async.parallel(callbacks, function (err) {
			if (err) {
				req.flash('error', err.message);
				return res.redirect(nconf.get('relative_path') + '/register/complete');
			}
 
			if (req.session.registration.register === true) {
				res.locals.processLogin = true;
				registerAndLoginUser(req, res, req.session.registration, done);
			} else {
				// Clear registration data in session
				done();
			}
		});
	});
};
 
authenticationController.registerAbort = function (req, res) {
	// End the session and redirect to home
	req.session.destroy(function () {
		res.redirect(nconf.get('relative_path') + '/');
	});
};
 
authenticationController.login = function (req, res, next) {
	if (plugins.hasListeners('action:auth.overrideLogin')) {
		return continueLogin(req, res, next);
	}
 
	var loginWith = meta.config.allowLoginWith || 'username-email';
 
	if (req.body.username && utils.isEmailValid(req.body.username) && loginWith.indexOf('email') !== -1) {
		user.getUsernameByEmail(req.body.username, function (err, username) {
			if (err) {
				return next(err);
			}
			req.body.username = username ? username : req.body.username;
			continueLogin(req, res, next);
		});
	} else if (loginWith.indexOf('username') !== -1 && !validator.isEmail(req.body.username)) {
		continueLogin(req, res, next);
	} else {
		res.status(500).send('[[error:wrong-login-type-' + loginWith + ']]');
	}
};
 
function continueLogin(req, res, next) {
	passport.authenticate('local', function (err, userData, info) {
		if (err) {
			return res.status(403).send(err.message);
		}
 
		if (!userData) {
			if (typeof info === 'object') {
				info = '[[error:invalid-username-or-password]]';
			}
 
			return res.status(403).send(info);
		}
 
		var passwordExpiry = userData.passwordExpiry !== undefined ? parseInt(userData.passwordExpiry, 10) : null;
 
		// Alter user cookie depending on passed-in option
		if (req.body.remember === 'on') {
			var duration = 1000 * 60 * 60 * 24 * (parseInt(meta.config.loginDays, 10) || 14);
			req.session.cookie.maxAge = duration;
			req.session.cookie.expires = new Date(Date.now() + duration);
		} else {
			req.session.cookie.maxAge = false;
			req.session.cookie.expires = false;
		}
 
		if (passwordExpiry && passwordExpiry < Date.now()) {
			winston.verbose('[auth] Triggering password reset for uid ' + userData.uid + ' due to password policy');
			req.session.passwordExpired = true;
			user.reset.generate(userData.uid, function (err, code) {
				if (err) {
					return res.status(403).send(err.message);
				}
 
				res.status(200).send(nconf.get('relative_path') + '/reset/' + code);
			});
		} else {
			authenticationController.doLogin(req, userData.uid, function (err) {
				if (err) {
					return res.status(403).send(err.message);
				}
 
				if (!req.session.returnTo) {
					res.status(200).send(nconf.get('relative_path') + '/');
				} else {
					var next = req.session.returnTo;
					delete req.session.returnTo;
 
					res.status(200).send(next);
				}
			});
		}
	})(req, res, next);
}
 
authenticationController.doLogin = function (req, uid, callback) {
	if (!uid) {
		return callback();
	}
 
	req.login({uid: uid}, function (err) {
		if (err) {
			return callback(err);
		}
 
		authenticationController.onSuccessfulLogin(req, uid, callback);
	});
};
 
authenticationController.onSuccessfulLogin = function (req, uid, callback) {
	callback = callback || function () {};
	var uuid = utils.generateUUID();
	req.session.meta = {};
 
	delete req.session.forceLogin;
 
	// Associate IP used during login with user account
	user.logIP(uid, req.ip);
	req.session.meta.ip = req.ip;
 
	// Associate metadata retrieved via user-agent
	req.session.meta = _.extend(req.session.meta, {
		uuid: uuid,
		datetime: Date.now(),
		platform: req.useragent.platform,
		browser: req.useragent.browser,
		version: req.useragent.version
	});
 
	// Associate login session with user
	async.parallel([
		function (next) {
			user.auth.addSession(uid, req.sessionID, next);
		},
		function (next) {
			db.setObjectField('uid:' + uid + 'sessionUUID:sessionId', uuid, req.sessionID, next);
		},
		function (next) {
			user.updateLastOnlineTime(uid, next);
		}
	], function (err) {
		if (err) {
			return callback(err);
		}
 
		// Force session check for all connected socket.io clients with the same session id
		sockets.in('sess_' + req.sessionID).emit('checkSession', uid);
 
		plugins.fireHook('action:user.loggedIn', uid);
		callback();
	});
};
 
authenticationController.localLogin = function (req, username, password, next) {
	if (!username) {
		return next(new Error('[[error:invalid-username]]'));
	}
 
	var userslug = utils.slugify(username);
	var uid, userData = {};
 
	async.waterfall([
		function (next) {
			user.isPasswordValid(password, next);
		},
		function (next) {
			user.getUidByUserslug(userslug, next);
		},
		function (_uid, next) {
			if (!_uid) {
				return next(new Error('[[error:no-user]]'));
			}
			uid = _uid;
			user.auth.logAttempt(uid, req.ip, next);
		},
		function (next) {
			async.parallel({
				userData: function (next) {
					db.getObjectFields('user:' + uid, ['password', 'passwordExpiry'], next);
				},
				isAdmin: function (next) {
					user.isAdministrator(uid, next);
				},
				banned: function (next) {
					user.isBanned(uid, next);
				}
			}, next);
		},
		function (result, next) {
			userData = result.userData;
			userData.uid = uid;
			userData.isAdmin = result.isAdmin;
 
			if (!result.isAdmin && parseInt(meta.config.allowLocalLogin, 10) === 0) {
				return next(new Error('[[error:local-login-disabled]]'));
			}
			if (!userData || !userData.password) {
				return next(new Error('[[error:invalid-user-data]]'));
			}
			if (result.banned) {
				// Retrieve ban reason and show error
				return user.getLatestBanInfo(uid, function (err, banInfo) {
					if (err) {
						if (err.message === 'no-ban-info') {
							next(new Error('[[error:user-banned]]'));
						} else {
							next(err);
						}
					} else if (banInfo.reason) {
						next(new Error('[[error:user-banned-reason, ' + banInfo.reason + ']]'));
					} else {
						next(new Error('[[error:user-banned]]'));
					}
				});
			}
 
			Password.compare(password, userData.password, next);
		},
		function (passwordMatch, next) {
			if (!passwordMatch) {
				return next(new Error('[[error:invalid-password]]'));
			}
			user.auth.clearLoginAttempts(uid);
			next(null, userData, '[[success:authentication-successful]]');
		}
	], next);
};
 
authenticationController.logout = function (req, res, next) {
	if (req.user && parseInt(req.user.uid, 10) > 0 && req.sessionID) {
		var uid = parseInt(req.user.uid, 10);
		var sessionID = req.sessionID;
 
		user.auth.revokeSession(sessionID, uid, function (err) {
			if (err) {
				return next(err);
			}
			req.logout();
			req.session.destroy();
 
			user.setUserField(uid, 'lastonline', Date.now() - 300000);
 
			plugins.fireHook('static:user.loggedOut', {req: req, res: res, uid: uid}, function () {
				res.status(200).send('');
 
				// Force session check for all connected socket.io clients with the same session id
				sockets.in('sess_' + sessionID).emit('checkSession', 0);
			});
		});
	} else {
		res.status(200).send('');
	}
};
 
 
module.exports = authenticationController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/categories.js

Statements: 12.9% (4 / 31)      Branches: 0% (0 / 22)      Functions: 0% (0 / 5)      Lines: 12.9% (4 / 31)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82    2 2 2   2                                                                                                                                                      
"use strict";
 
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
 
var categories = require('../categories');
var meta = require('../meta');
var helpers = require('./helpers');
 
var categoriesController = {};
 
categoriesController.list = function (req, res, next) {
	res.locals.metaTags = [{
		name: "title",
		content: validator.escape(String(meta.config.title || 'NodeBB'))
	}, {
		name: "description",
		content: validator.escape(String(meta.config.description || ''))
	}, {
		property: 'og:title',
		content: '[[pages:categories]]'
	}, {
		property: 'og:type',
		content: 'website'
	}];
 
	var ogImage = meta.config['og:image'] || meta.config['brand:logo'] || '';
	if (ogImage) {
		if (!ogImage.startsWith('http')) {
			ogImage = nconf.get('url') + ogImage;
		}
		res.locals.metaTags.push({
			property: 'og:image',
			content: ogImage
		});
	}
 
	var categoryData;
	async.waterfall([
		function (next) {
			categories.getCategoriesByPrivilege('cid:0:children', req.uid, 'find', next);
		},
		function (_categoryData, next) {
			categoryData = _categoryData;
 
			var allCategories = [];
			categories.flattenCategories(allCategories, categoryData);
 
			categories.getRecentTopicReplies(allCategories, req.uid, next);
		}
	], function (err) {
		if (err) {
			return next(err);
		}
 
		var data = {
			title: '[[pages:categories]]',
			categories: categoryData
		};
 
		if (req.path.startsWith('/api/categories') || req.path.startsWith('/categories')) {
			data.breadcrumbs = helpers.buildBreadcrumbs([{text: data.title}]);
		}
 
		data.categories.forEach(function (category) {
			if (category && Array.isArray(category.posts) && category.posts.length) {
				category.teaser = {
					url: nconf.get('relative_path') + '/topic/' + category.posts[0].topic.slug + '/' + category.posts[0].index,
					timestampISO: category.posts[0].timestampISO,
					pid: category.posts[0].pid
				};
			}
		});
 
		res.render('categories', data);
	});
};
 
module.exports = categoriesController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/category.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/category.js

Statements: 3% (3 / 100)      Branches: 0% (0 / 62)      Functions: 0% (0 / 14)      Lines: 3% (3 / 100)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220      2 2   2                                                                                                                                                                                                                                                                                                                                                                                                                                          
"use strict";
 
 
var async = require('async');
var nconf = require('nconf');
 
var db = require('../database');
var privileges = require('../privileges');
var user = require('../user');
var categories = require('../categories');
var meta = require('../meta');
var pagination = require('../pagination');
var helpers = require('./helpers');
var utils = require('../../public/src/utils');
 
var categoryController = {};
 
categoryController.get = function (req, res, callback) {
	var cid = req.params.category_id;
	var currentPage = parseInt(req.query.page, 10) || 1;
	var pageCount = 1;
	var userPrivileges;
	var settings;
 
	if ((req.params.topic_index && !utils.isNumber(req.params.topic_index)) || !utils.isNumber(cid)) {
		return callback();
	}
 
	async.waterfall([
		function (next) {
			async.parallel({
				categoryData: function (next) {
					categories.getCategoryFields(cid, ['slug', 'disabled', 'topic_count'], next);
				},
				privileges: function (next) {
					privileges.categories.get(cid, req.uid, next);
				},
				userSettings: function (next) {
					user.getSettings(req.uid, next);
				}
			}, next);
		},
		function (results, next) {
			userPrivileges = results.privileges;
 
			if (!results.categoryData.slug || (results.categoryData && parseInt(results.categoryData.disabled, 10) === 1)) {
				return callback();
			}
 
			if (!results.privileges.read) {
				return helpers.notAllowed(req, res);
			}
 
			if (!res.locals.isAPI && (!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) {
				return helpers.redirect(res, '/category/' + results.categoryData.slug);
			}
 
			settings = results.userSettings;
			var topicIndex = utils.isNumber(req.params.topic_index) ? parseInt(req.params.topic_index, 10) - 1 : 0;
			var topicCount = parseInt(results.categoryData.topic_count, 10);
			pageCount = Math.max(1, Math.ceil(topicCount / settings.topicsPerPage));
 
			if (topicIndex < 0 || topicIndex > Math.max(topicCount - 1, 0)) {
				return helpers.redirect(res, '/category/' + cid + '/' + req.params.slug + (topicIndex > topicCount ? '/' + topicCount : ''));
			}
 
			if (settings.usePagination && (currentPage < 1 || currentPage > pageCount)) {
				return callback();
			}
 
			if (!settings.usePagination) {
				topicIndex = Math.max(topicIndex - (settings.topicsPerPage - 1), 0);
			} else if (!req.query.page) {
				var index = Math.max(parseInt((topicIndex || 0), 10), 0);
				currentPage = Math.ceil((index + 1) / settings.topicsPerPage);
				topicIndex = 0;
			}
 
			var set = 'cid:' + cid + ':tids';
			var reverse = false;
			// `sort` qs has priority over user setting
			var sort = req.query.sort || settings.categoryTopicSort;
			if (sort === 'newest_to_oldest') {
				reverse = true;
			} else if (sort === 'most_posts') {
				reverse = true;
				set = 'cid:' + cid + ':tids:posts';
			}
 
			var start = (currentPage - 1) * settings.topicsPerPage + topicIndex;
			var stop = start + settings.topicsPerPage - 1;
 
			var payload = {
				cid: cid,
				set: set,
				reverse: reverse,
				start: start,
				stop: stop,
				uid: req.uid,
				settings: settings
			};
 
			async.waterfall([
				function (next) {
					user.getUidByUserslug(req.query.author, next);
				},
				function (uid, next) {
					payload.targetUid = uid;
					if (uid) {
						payload.set = 'cid:' + cid + ':uid:' + uid + ':tids';
					}
 
					if (req.query.tag) {
						payload.set = [payload.set, 'tag:' + req.query.tag + ':topics'];
					}
					categories.getCategoryById(payload, next);
				}
			], next);
		},
		function (categoryData, next) {
 
			categories.modifyTopicsByPrivilege(categoryData.topics, userPrivileges);
 
			if (categoryData.link) {
				db.incrObjectField('category:' + categoryData.cid, 'timesClicked');
				return res.redirect(categoryData.link);
			}
 
			var breadcrumbs = [
				{
					text: categoryData.name,
					url: nconf.get('relative_path') + '/category/' + categoryData.slug
				}
			];
			helpers.buildCategoryBreadcrumbs(categoryData.parentCid, function (err, crumbs) {
				if (err) {
					return next(err);
				}
				categoryData.breadcrumbs = crumbs.concat(breadcrumbs);
				next(null, categoryData);
			});
		},
		function (categoryData, next) {
			if (!categoryData.children.length) {
				return next(null, categoryData);
			}
			var allCategories = [];
			categories.flattenCategories(allCategories, categoryData.children);
			categories.getRecentTopicReplies(allCategories, req.uid, function (err) {
				next(err, categoryData);
			});
		}
	], function (err, categoryData) {
		if (err) {
			return callback(err);
		}
 
		categoryData.privileges = userPrivileges;
		categoryData.showSelect = categoryData.privileges.editable;
 
		res.locals.metaTags = [
			{
				name: 'title',
				content: categoryData.name
			},
			{
				property: 'og:title',
				content: categoryData.name
			},
			{
				name: 'description',
				content: categoryData.description
			},
			{
				property: "og:type",
				content: 'website'
			}
		];
 
		if (categoryData.backgroundImage) {
			res.locals.metaTags.push({
				name: 'og:image',
				content: categoryData.backgroundImage
			});
		}
 
		res.locals.linkTags = [
			{
				rel: 'alternate',
				type: 'application/rss+xml',
				href: nconf.get('url') + '/category/' + cid + '.rss'
			},
			{
				rel: 'up',
				href: nconf.get('url')
			}
		];
 
		if (parseInt(req.uid, 10)) {
			categories.markAsRead([cid], req.uid);
		}
 
		categoryData['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
		categoryData.rssFeedUrl = nconf.get('relative_path') + '/category/' + categoryData.cid + '.rss';
		categoryData.title = categoryData.name;
		pageCount = Math.max(1, Math.ceil(categoryData.topic_count / settings.topicsPerPage));
		categoryData.pagination = pagination.create(currentPage, pageCount, req.query);
		categoryData.pagination.rel.forEach(function (rel) {
			rel.href = nconf.get('url') + '/category/' + categoryData.slug + rel.href;
			res.locals.linkTags.push(rel);
		});
 
		res.render('category', categoryData);
	});
};
 
 
module.exports = categoryController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/globalmods.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/globalmods.js

Statements: 11.11% (1 / 9)      Branches: 0% (0 / 4)      Functions: 0% (0 / 2)      Lines: 11.11% (1 / 9)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20    2                                  
"use strict";
 
var user = require('../user');
var adminBlacklistController = require('./admin/blacklist');
 
var globalModsController = {};
 
globalModsController.ipBlacklist = function (req, res, next) {
	user.isAdminOrGlobalMod(req.uid, function (err, isAdminOrGlobalMod) {
		if (err || !isAdminOrGlobalMod) {
			return next(err);
		}
 
		adminBlacklistController.get(req, res, next);
	});
};
 
module.exports = globalModsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/groups.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/groups.js

Statements: 5.13% (4 / 78)      Branches: 0% (0 / 44)      Functions: 0% (0 / 22)      Lines: 5.13% (4 / 78)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179    2 2 2   2                                                                                                                                                                                                                                                                                                                                                        
"use strict";
 
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
 
var meta = require('../meta');
var groups = require('../groups');
var user = require('../user');
var helpers = require('./helpers');
 
var groupsController = {};
 
groupsController.list = function (req, res, next) {
	var sort = req.query.sort || 'alpha';
 
	groupsController.getGroupsFromSet(req.uid, sort, 0, 14, function (err, data) {
		if (err) {
			return next(err);
		}
		data.title = '[[pages:groups]]';
		data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[pages:groups]]'}]);
		res.render('groups/list', data);
	});
};
 
groupsController.getGroupsFromSet = function (uid, sort, start, stop, callback) {
	var set = 'groups:visible:name';
	if (sort === 'count') {
		set = 'groups:visible:memberCount';
	} else if (sort === 'date') {
		set = 'groups:visible:createtime';
	}
 
	groups.getGroupsFromSet(set, uid, start, stop, function (err, groups) {
		if (err) {
			return callback(err);
		}
 
		callback(null, {
			groups: groups,
			allowGroupCreation: parseInt(meta.config.allowGroupCreation, 10) === 1,
			nextStart: stop + 1
		});
	});
};
 
groupsController.details = function (req, res, callback) {
	var groupName;
	async.waterfall([
		function (next) {
			groups.getGroupNameByGroupSlug(req.params.slug, next);
		},
		function (_groupName, next) {
			groupName = _groupName;
			if (!groupName) {
				return callback();
			}
			async.parallel({
				exists: async.apply(groups.exists, groupName),
				hidden: async.apply(groups.isHidden, groupName)
			}, next);
		},
		function (results, next) {
			if (!results.exists) {
				return callback();
			}
			if (!results.hidden) {
				return next();
			}
			async.parallel({
				isMember: async.apply(groups.isMember, req.uid, groupName),
				isInvited: async.apply(groups.isInvited, req.uid, groupName)
			}, function (err, checks) {
				if (err || checks.isMember || checks.isInvited) {
					return next(err);
				}
				callback();
			});
		},
		function (next) {
			async.parallel({
				group: function (next) {
					groups.get(groupName, {
						uid: req.uid,
						truncateUserList: true,
						userListCount: 20
					}, next);
				},
				posts: function (next) {
					groups.getLatestMemberPosts(groupName, 10, req.uid, next);
				},
				isAdmin:function (next) {
					user.isAdministrator(req.uid, next);
				},
				isGlobalMod: function (next) {
					user.isGlobalModerator(req.uid, next);
				}
			}, next);
		}
	], function (err, results) {
		if (err) {
			return callback(err);
		}
 
		if (!results.group) {
			return callback();
		}
		results.group.isOwner = results.group.isOwner || results.isAdmin || (results.isGlobalMod && !results.group.system);
		results.title = '[[pages:group, ' + results.group.displayName + ']]';
		results.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[pages:groups]]', url: '/groups' }, {text: results.group.displayName}]);
		results.allowPrivateGroups = parseInt(meta.config.allowPrivateGroups, 10) === 1;
 
		res.render('groups/details', results);
	});
};
 
groupsController.members = function (req, res, callback) {
	var groupName;
	async.waterfall([
		function (next) {
			groups.getGroupNameByGroupSlug(req.params.slug, next);
		},
		function (_groupName, next) {
			if (!_groupName) {
				return callback();
			}
			groupName = _groupName;
			async.parallel({
				isAdminOrGlobalMod: async.apply(user.isAdminOrGlobalMod, req.uid),
				isMember: async.apply(groups.isMember, req.uid, groupName),
				isHidden: async.apply(groups.isHidden, groupName)
			}, next);
		},
		function (results, next) {
			if (results.isHidden && !results.isMember && !results.isAdminOrGlobalMod) {
				return callback();
			}
 
			user.getUsersFromSet('group:' + groupName + ':members', req.uid, 0, 49, next);
		},
	], function (err, users) {
		if (err) {
			return callback(err);
		}
 
		var breadcrumbs = helpers.buildBreadcrumbs([
			{text: '[[pages:groups]]', url: '/groups' },
			{text: validator.escape(String(groupName)), url: '/groups/' + req.params.slug},
			{text: '[[groups:details.members]]'}
		]);
 
		res.render('groups/members', {
			users: users,
			nextStart: 50,
			loadmore_display: users.length > 50 ? 'block' : 'hide',
			breadcrumbs: breadcrumbs
		});
	});
};
 
groupsController.uploadCover = function (req, res, next) {
	var params = JSON.parse(req.body.params);
 
	groups.updateCover(req.uid, {
		file: req.files.files[0].path,
		groupName: params.groupName
	}, function (err, image) {
		if (err) {
			return next(err);
		}
 
		res.json([{url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
	});
};
 
module.exports = groupsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/helpers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/helpers.js

Statements: 7.23% (6 / 83)      Branches: 0% (0 / 35)      Functions: 0% (0 / 23)      Lines: 7.23% (6 / 83)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181    2 2 2 2   2                                                                                                                                                                                                                                                                                                                                   1                      
'use strict';
 
var nconf = require('nconf');
var async = require('async');
var validator = require('validator');
var winston = require('winston');
 
var user = require('../user');
var privileges = require('../privileges');
var categories = require('../categories');
var plugins = require('../plugins');
var meta = require('../meta');
 
var helpers = {};
 
helpers.notAllowed = function (req, res, error) {
	plugins.fireHook('filter:helpers.notAllowed', {
		req: req,
		res: res,
		error: error
	}, function (err, data) {
		if (err) {
			return winston.error(err);
		}
		if (req.uid) {
			if (res.locals.isAPI) {
				res.status(403).json({
					path: req.path.replace(/^\/api/, ''),
					loggedIn: !!req.uid, error: error,
					title: '[[global:403.title]]'
				});
			} else {
				res.status(403).render('403', {
					path: req.path,
					loggedIn: !!req.uid, error: error,
					title: '[[global:403.title]]'
				});
			}
		} else {
			if (res.locals.isAPI) {
				req.session.returnTo = nconf.get('relative_path') + req.url.replace(/^\/api/, '');
				res.status(401).json('not-authorized');
			} else {
				req.session.returnTo = nconf.get('relative_path') + req.url;
				res.redirect(nconf.get('relative_path') + '/login');
			}
		}
	});
};
 
helpers.redirect = function (res, url) {
	if (res.locals.isAPI) {
		res.status(308).json(url);
	} else {
		res.redirect(nconf.get('relative_path') + encodeURI(url));
	}
};
 
helpers.buildCategoryBreadcrumbs = function (cid, callback) {
	var breadcrumbs = [];
 
	async.whilst(function () {
		return parseInt(cid, 10);
	}, function (next) {
		categories.getCategoryFields(cid, ['name', 'slug', 'parentCid', 'disabled'], function (err, data) {
			if (err) {
				return next(err);
			}
 
			if (!parseInt(data.disabled, 10)) {
				breadcrumbs.unshift({
					text: validator.escape(String(data.name)),
					url: nconf.get('relative_path') + '/category/' + data.slug
				});
			}
 
			cid = data.parentCid;
			next();
		});
	}, function (err) {
		if (err) {
			return callback(err);
		}
 
		if (!meta.config.homePageRoute && meta.config.homePageCustom) {
			breadcrumbs.unshift({
				text: '[[global:header.categories]]',
				url: nconf.get('relative_path') + '/categories'
			});
		}
 
		breadcrumbs.unshift({
			text: '[[global:home]]',
			url: nconf.get('relative_path') + '/'
		});
 
		callback(null, breadcrumbs);
	});
};
 
helpers.buildBreadcrumbs = function (crumbs) {
	var breadcrumbs = [
		{
			text: '[[global:home]]',
			url: nconf.get('relative_path') + '/'
		}
	];
 
	crumbs.forEach(function (crumb) {
		if (crumb) {
			if (crumb.url) {
				crumb.url = nconf.get('relative_path') + crumb.url;
			}
			breadcrumbs.push(crumb);
		}
	});
 
	return breadcrumbs;
};
 
helpers.buildTitle = function (pageTitle) {
	var titleLayout = meta.config.titleLayout || '{pageTitle} | {browserTitle}';
 
	var browserTitle = validator.escape(String(meta.config.browserTitle || meta.config.title || 'NodeBB'));
	pageTitle = pageTitle || '';
	var title = titleLayout.replace('{pageTitle}', function () {
		return pageTitle;
	}).replace('{browserTitle}', function () {
		return browserTitle;
	});
	return title;
};
 
helpers.getWatchedCategories = function (uid, selectedCid, callback) {
	async.waterfall([
		function (next) {
			user.getWatchedCategories(uid, next);
		},
		function (cids, next) {
			privileges.categories.filterCids('read', cids, uid, next);
		},
		function (cids, next) {
			categories.getCategoriesFields(cids, ['cid', 'name', 'slug', 'icon', 'link', 'color', 'bgColor', 'parentCid'], next);
		},
		function (categoryData, next) {
			categoryData = categoryData.filter(function (category) {
				return category && !category.link;
			});
 
			var selectedCategory;
			categoryData.forEach(function (category) {
				category.selected = parseInt(category.cid, 10) === parseInt(selectedCid, 10);
				if (category.selected) {
					selectedCategory = category;
				}
			});
 
			var categoriesData = [];
			var tree = categories.getTree(categoryData, 0);
 
			tree.forEach(function (category) {
				recursive(category, categoriesData, '');
			});
 
			next(null, {categories: categoriesData, selectedCategory: selectedCategory});
		}
	], callback);
};
 
function recursive(category, categoriesData, level) {
	category.level = level;
	categoriesData.push(category);
 
	category.children.forEach(function (child) {
		recursive(child, categoriesData, '&nbsp;&nbsp;&nbsp;&nbsp;' + level);
	});
}
 
module.exports = helpers;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/index.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/index.js

Statements: 2.43% (5 / 206)      Branches: 0% (0 / 159)      Functions: 0% (0 / 29)      Lines: 2.43% (5 / 206)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454    6 6 6 6   6                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
"use strict";
 
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
var winston = require('winston');
 
var meta = require('../meta');
var user = require('../user');
var plugins = require('../plugins');
var helpers = require('./helpers');
 
var Controllers = {
	topics: require('./topics'),
	posts: require('./posts'),
	categories: require('./categories'),
	category: require('./category'),
	unread: require('./unread'),
	recent: require('./recent'),
	popular: require('./popular'),
	tags: require('./tags'),
	search: require('./search'),
	users: require('./users'),
	groups: require('./groups'),
	accounts: require('./accounts'),
	authentication: require('./authentication'),
	api: require('./api'),
	admin: require('./admin'),
	globalMods: require('./globalmods'),
	mods: require('./mods'),
	sitemap: require('./sitemap')
};
 
 
Controllers.home = function (req, res, next) {
	var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
 
	user.getSettings(req.uid, function (err, settings) {
		if (err) {
			return next(err);
		}
		if (parseInt(meta.config.allowUserHomePage, 10) === 1 && settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
			route = settings.homePageRoute || route;
		}
 
		var hook = 'action:homepage.get:' + route;
 
		if (plugins.hasListeners(hook)) {
			return plugins.fireHook(hook, {req: req, res: res, next: next});
		}
 
		if (route === 'categories' || route === '/') {
			Controllers.categories.list(req, res, next);
		} else if (route === 'unread') {
			Controllers.unread.get(req, res, next);
		} else if (route === 'recent') {
			Controllers.recent.get(req, res, next);
		} else if (route === 'popular') {
			Controllers.popular.get(req, res, next);
		} else {
			var match = /^category\/(\d+)\/(.*)$/.exec(route);
 
			if (match) {
				req.params.topic_index = "1";
				req.params.category_id = match[1];
				req.params.slug = match[2];
				Controllers.category.get(req, res, next);
			} else {
				res.redirect(route);
			}
		}
	});
};
 
Controllers.reset = function (req, res, next) {
	if (req.params.code) {
		user.reset.validate(req.params.code, function (err, valid) {
			if (err) {
				return next(err);
			}
			res.render('reset_code', {
				valid: valid,
				displayExpiryNotice: req.session.passwordExpired,
				code: req.params.code,
				minimumPasswordLength: parseInt(meta.config.minimumPasswordLength, 10),
				breadcrumbs: helpers.buildBreadcrumbs([{text: '[[reset_password:reset_password]]', url: '/reset'}, {text: '[[reset_password:update_password]]'}]),
				title: '[[pages:reset]]'
			});
 
			delete req.session.passwordExpired;
		});
	} else {
		res.render('reset', {
			code: null,
			breadcrumbs: helpers.buildBreadcrumbs([{text: '[[reset_password:reset_password]]'}]),
			title: '[[pages:reset]]'
		});
	}
};
 
Controllers.login = function (req, res, next) {
	var data = {};
	var loginStrategies = require('../routes/authentication').getLoginStrategies();
	var registrationType = meta.config.registrationType || 'normal';
 
	var allowLoginWith = (meta.config.allowLoginWith || 'username-email');
	var returnTo = (req.headers['x-return-to'] || '').replace(nconf.get('base_url'), '');
 
	var errorText;
	if (req.query.error === 'csrf-invalid') {
		errorText = '[[error:csrf-invalid]]';
	} else if (req.query.error) {
		errorText = validator.escape(String(req.query.error));
	}
 
	if (returnTo) {
		req.session.returnTo = returnTo;
	}
 
	data.alternate_logins = loginStrategies.length > 0;
	data.authentication = loginStrategies;
	data.allowLocalLogin = parseInt(meta.config.allowLocalLogin, 10) === 1 || parseInt(req.query.local, 10) === 1;
	data.allowRegistration = registrationType === 'normal' || registrationType === 'admin-approval' || registrationType === 'admin-approval-ip';
	data.allowLoginWith = '[[login:' + allowLoginWith + ']]';
	data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:login]]'}]);
	data.error = req.flash('error')[0] || errorText;
	data.title = '[[pages:login]]';
 
	if (!data.allowLocalLogin && !data.allowRegistration && data.alternate_logins && data.authentication.length === 1) {
		if (res.locals.isAPI) {
			return helpers.redirect(res, {
				external: data.authentication[0].url
			});
		} else {
			return res.redirect(nconf.get('relative_path') + data.authentication[0].url);
		}
	}
	if (req.uid) {
		user.getUserFields(req.uid, ['username', 'email'], function (err, user) {
			if (err) {
				return next(err);
			}
			data.username = allowLoginWith === 'email' ? user.email : user.username;
			data.alternate_logins = [];
			res.render('login', data);
		});
	} else {
		res.render('login', data);
	}
 
};
 
Controllers.register = function (req, res, next) {
	var registrationType = meta.config.registrationType || 'normal';
 
	if (registrationType === 'disabled') {
		return next();
	}
 
	var errorText;
	if (req.query.error === 'csrf-invalid') {
		errorText = '[[error:csrf-invalid]]';
	}
 
	async.waterfall([
		function (next) {
			if (registrationType === 'invite-only' || registrationType === 'admin-invite-only') {
				user.verifyInvitation(req.query, next);
			} else {
				next();
			}
		},
		function (next) {
			plugins.fireHook('filter:parse.post', {postData: {content: meta.config.termsOfUse || ''}}, next);
		}
	], function (err, termsOfUse) {
		if (err) {
			return next(err);
		}
		var loginStrategies = require('../routes/authentication').getLoginStrategies();
		var data = {
			'register_window:spansize': loginStrategies.length ? 'col-md-6' : 'col-md-12',
			'alternate_logins': !!loginStrategies.length
		};
 
		data.authentication = loginStrategies;
 
		data.minimumUsernameLength = parseInt(meta.config.minimumUsernameLength, 10);
		data.maximumUsernameLength = parseInt(meta.config.maximumUsernameLength, 10);
		data.minimumPasswordLength = parseInt(meta.config.minimumPasswordLength, 10);
		data.termsOfUse = termsOfUse.postData.content;
		data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[register:register]]'}]);
		data.regFormEntry = [];
		data.error = req.flash('error')[0] || errorText;
		data.title = '[[pages:register]]';
 
		res.render('register', data);
	});
};
 
Controllers.registerInterstitial = function (req, res, next) {
	if (!req.session.hasOwnProperty('registration')) {
		return res.redirect(nconf.get('relative_path') + '/register');
	}
 
	plugins.fireHook('filter:register.interstitial', {
		userData: req.session.registration,
		interstitials: []
	}, function (err, data) {
		if (err) {
			return next(err);
		}
 
		if (!data.interstitials.length) {
			return next();
		}
 
		var renders = data.interstitials.map(function (interstitial) {
			return async.apply(req.app.render.bind(req.app), interstitial.template, interstitial.data || {});
		});
		var errors = req.flash('error');
 
		async.parallel(renders, function (err, sections) {
			if (err) {
				return next(err);
			}
 
			res.render('registerComplete', {
				title: '[[pages:registration-complete]]',
				errors: errors,
				sections: sections
			});
		});
	});
};
 
Controllers.compose = function (req, res, next) {
	plugins.fireHook('filter:composer.build', {
		req: req,
		res: res,
		next: next,
		templateData: {}
	}, function (err, data) {
		if (err) {
			return next(err);
		}
 
		if (data.templateData.disabled) {
			res.render('', {
				title: '[[modules:composer.compose]]'
			});
		} else {
			data.templateData.title = '[[modules:composer.compose]]';
			res.render('compose', data.templateData);
		}
	});
};
 
Controllers.confirmEmail = function (req, res) {
	user.email.confirm(req.params.code, function (err) {
		res.render('confirm', {
			error: err ? err.message : '',
			title: '[[pages:confirm]]',
		});
	});
};
 
Controllers.robots = function (req, res) {
	res.set('Content-Type', 'text/plain');
 
	if (meta.config["robots.txt"]) {
		res.send(meta.config["robots.txt"]);
	} else {
		res.send("User-agent: *\n" +
			"Disallow: " + nconf.get('relative_path') + "/admin/\n" +
			"Sitemap: " + nconf.get('url') + "/sitemap.xml");
	}
};
 
Controllers.manifest = function (req, res) {
	var manifest = {
		name: meta.config.title || 'NodeBB',
		start_url: nconf.get('relative_path') + '/',
		display: 'standalone',
		orientation: 'portrait',
		icons: []
	};
 
	if (meta.config['brand:touchIcon']) {
		manifest.icons.push({
			src: nconf.get('relative_path') + '/uploads/system/touchicon-36.png',
			sizes: '36x36',
			type: 'image/png',
			density: 0.75
		}, {
			src: nconf.get('relative_path') + '/uploads/system/touchicon-48.png',
			sizes: '48x48',
			type: 'image/png',
			density: 1.0
		}, {
			src: nconf.get('relative_path') + '/uploads/system/touchicon-72.png',
			sizes: '72x72',
			type: 'image/png',
			density: 1.5
		}, {
			src: nconf.get('relative_path') + '/uploads/system/touchicon-96.png',
			sizes: '96x96',
			type: 'image/png',
			density: 2.0
		}, {
			src: nconf.get('relative_path') + '/uploads/system/touchicon-144.png',
			sizes: '144x144',
			type: 'image/png',
			density: 3.0
		}, {
			src: nconf.get('relative_path') + '/uploads/system/touchicon-192.png',
			sizes: '192x192',
			type: 'image/png',
			density: 4.0
		});
	}
 
	res.status(200).json(manifest);
};
 
Controllers.outgoing = function (req, res) {
	var url = req.query.url || '';
	var data = {
		outgoing: validator.escape(String(url)),
		title: meta.config.title,
		breadcrumbs: helpers.buildBreadcrumbs([{text: '[[notifications:outgoing_link]]'}])
	};
 
	if (url) {
		res.render('outgoing', data);
	} else {
		res.status(404).redirect(nconf.get('relative_path') + '/404');
	}
};
 
Controllers.termsOfUse = function (req, res, next) {
	if (!meta.config.termsOfUse) {
		return next();
	}
	res.render('tos', {termsOfUse: meta.config.termsOfUse});
};
 
Controllers.ping = function (req, res) {
	res.status(200).send(req.path === '/sping' ? 'healthy' : '200');
};
 
Controllers.handle404 = function (req, res) {
	var relativePath = nconf.get('relative_path');
	var isLanguage = new RegExp('^' + relativePath + '/api/language/.*/.*');
	var isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js');
 
	if (plugins.hasListeners('action:meta.override404')) {
		return plugins.fireHook('action:meta.override404', {
			req: req,
			res: res,
			error: {}
		});
	}
 
	if (isClientScript.test(req.url)) {
		res.type('text/javascript').status(200).send('');
	} else if (isLanguage.test(req.url)) {
		res.status(200).json({});
	} else if (req.path.startsWith(relativePath + '/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') {
		meta.errors.log404(req.path || '');
		res.sendStatus(404);
	} else if (req.accepts('html')) {
		if (process.env.NODE_ENV === 'development') {
			winston.warn('Route requested but not found: ' + req.url);
		}
 
		meta.errors.log404(req.path.replace(/^\/api/, '') || '');
		res.status(404);
 
		var path = String(req.path || '');
 
		if (res.locals.isAPI) {
			return res.json({path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]'});
		}
		var middleware = require('../middleware');
		middleware.buildHeader(req, res, function () {
			res.render('404', {path: validator.escape(path), title: '[[global:404.title]]'});
		});
	} else {
		res.status(404).type('txt').send('Not found');
	}
};
 
Controllers.handleURIErrors = function (err, req, res, next) {
	// Handle cases where malformed URIs are passed in
	if (err instanceof URIError) {
		var tidMatch = req.path.match(/^\/topic\/(\d+)\//);
		var cidMatch = req.path.match(/^\/category\/(\d+)\//);
 
		if (tidMatch) {
			res.redirect(nconf.get('relative_path') + tidMatch[0]);
		} else if (cidMatch) {
			res.redirect(nconf.get('relative_path') + cidMatch[0]);
		} else {
			winston.warn('[controller] Bad request: ' + req.path);
			if (res.locals.isAPI) {
				res.status(400).json({
					error: '[[global:400.title]]'
				});
			} else {
				var middleware = require('../middleware');
				middleware.buildHeader(req, res, function () {
					res.render('400', { error: validator.escape(String(err.message)) });
				});
			}
		}
 
		return;
	} else {
		next(err);
	}
};
 
Controllers.handleErrors = function (err, req, res, next) {
	switch (err.code) {
		case 'EBADCSRFTOKEN':
			winston.error(req.path + '\n', err.message);
			return res.sendStatus(403);
		case 'blacklisted-ip':
			return res.status(403).type('text/plain').send(err.message);
	}
 
	if (parseInt(err.status, 10) === 302 && err.path) {
		return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path);
	}
 
	winston.error(req.path + '\n', err.stack);
 
	res.status(err.status || 500);
 
	var path = String(req.path || '');
	if (res.locals.isAPI) {
		res.json({path: validator.escape(path), error: err.message});
	} else {
		var middleware = require('../middleware');
		middleware.buildHeader(req, res, function () {
			res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) });
		});
	}
};
 
module.exports = Controllers;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/mods.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/mods.js

Statements: 16.67% (2 / 12)      Branches: 0% (0 / 10)      Functions: 0% (0 / 2)      Lines: 16.67% (2 / 12)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29    2   2                                                
"use strict";
 
var async = require('async');
 
var user = require('../user');
var adminFlagsController = require('./admin/flags');
 
var modsController = {};
 
modsController.flagged = function (req, res, next) {
	async.parallel({
		isAdminOrGlobalMod: async.apply(user.isAdminOrGlobalMod, req.uid),
		moderatedCids: async.apply(user.getModeratedCids, req.uid)
	}, function (err, results) {
		if (err || !(results.isAdminOrGlobalMod || !!results.moderatedCids.length)) {
			return next(err);
		}
 
		if (!results.isAdminOrGlobalMod && results.moderatedCids.length) {
			res.locals.cids = results.moderatedCids;
		}
 
		adminFlagsController.get(req, res, next);
	});
};
 
module.exports = modsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/popular.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/popular.js

Statements: 6.45% (2 / 31)      Branches: 0% (0 / 24)      Functions: 0% (0 / 2)      Lines: 6.45% (2 / 31)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75      2 2                                                                                                                                            
 
'use strict';
 
var nconf = require('nconf');
var topics = require('../topics');
var meta = require('../meta');
var helpers = require('./helpers');
 
var popularController = {};
 
var anonCache = {};
var lastUpdateTime = 0;
 
var terms = {
	daily: 'day',
	weekly: 'week',
	monthly: 'month'
};
 
popularController.get = function (req, res, next) {
 
	var term = terms[req.params.term];
 
	if (!term && req.params.term) {
		return next();
	}
	term = term || 'alltime';
 
	var termToBreadcrumb = {
		day: '[[recent:day]]',
		week: '[[recent:week]]',
		month: '[[recent:month]]',
		alltime: '[[global:header.popular]]'
	};
 
	if (!req.uid) {
		if (anonCache[term] && (Date.now() - lastUpdateTime) < 60 * 60 * 1000) {
			return res.render('popular', anonCache[term]);
		}
	}
 
	topics.getPopular(term, req.uid, meta.config.topicsPerList, function (err, topics) {
		if (err) {
			return next(err);
		}
 
		var data = {
			topics: topics,
			'feeds:disableRSS': parseInt(meta.config['feeds:disableRSS'], 10) === 1,
			rssFeedUrl: nconf.get('relative_path') + '/popular/' + (req.params.term || 'daily') + '.rss',
			title: '[[pages:popular-' + term + ']]',
			term: term
		};
 
		if (req.path.startsWith('/api/popular') || req.path.startsWith('/popular')) {
			var breadcrumbs = [{text: termToBreadcrumb[term]}];
 
			if (req.params.term) {
				breadcrumbs.unshift({text: '[[global:header.popular]]', url: '/popular'});
			}
 
			data.breadcrumbs = helpers.buildBreadcrumbs(breadcrumbs);
		}
 
		if (!req.uid) {
			anonCache[term] = data;
			lastUpdateTime = Date.now();
		}
 
		res.render('popular', data);
	});
};
 
module.exports = popularController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/posts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/posts.js

Statements: 8.33% (1 / 12)      Branches: 0% (0 / 6)      Functions: 0% (0 / 2)      Lines: 8.33% (1 / 12)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26    2                                              
"use strict";
 
var posts = require('../posts');
var helpers = require('./helpers');
 
var postsController = {};
 
postsController.redirectToPost = function (req, res, callback) {
	var pid = parseInt(req.params.pid, 10);
	if (!pid) {
		return callback();
	}
 
	posts.generatePostPath(pid, req.uid, function (err, path) {
		if (err || !path) {
			return callback(err);
		}
 
		helpers.redirect(res, path);
	});
};
 
 
module.exports = postsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/recent.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/recent.js

Statements: 8.51% (4 / 47)      Branches: 0% (0 / 16)      Functions: 0% (0 / 7)      Lines: 8.51% (4 / 47)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96      2 2 2   2                                                                                                                                                                                
 
'use strict';
 
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
 
var user = require('../user');
var topics = require('../topics');
var meta = require('../meta');
var helpers = require('./helpers');
var pagination = require('../pagination');
 
var recentController = {};
 
var validFilter = {'': true, 'new': true, 'watched': true};
 
recentController.get = function (req, res, next) {
	var page = parseInt(req.query.page, 10) || 1;
	var stop = 0;
	var settings;
	var cid = req.query.cid;
	var filter = req.params.filter || '';
	var categoryData;
 
	if (!validFilter[filter]) {
		return next();
	}
 
	async.waterfall([
		function (next) {
			async.parallel({
				settings: function (next) {
					user.getSettings(req.uid, next);
				},
				watchedCategories: function (next) {
					helpers.getWatchedCategories(req.uid, cid, next);
				}
			}, next);
		},
		function (results, next) {
			settings = results.settings;
			categoryData = results.watchedCategories;
 
			var start = Math.max(0, (page - 1) * settings.topicsPerPage);
			stop = start + settings.topicsPerPage - 1;
 
			topics.getRecentTopics(cid, req.uid, start, stop, filter, next);
		}
	], function (err, data) {
		if (err) {
			return next(err);
		}
 
		data.categories = categoryData.categories;
		data.selectedCategory = categoryData.selectedCategory;
		data.nextStart = stop + 1;
		data.set = 'topics:recent';
		data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
		data.rssFeedUrl = nconf.get('relative_path') + '/recent.rss';
		data.title = '[[pages:recent]]';
		data.filters = [{
			name: '[[unread:all-topics]]',
			url: 'recent',
			selected: filter === '',
			filter: ''
		}, {
			name: '[[unread:new-topics]]',
			url: 'recent/new',
			selected: filter === 'new',
			filter: 'new'
		}, {
			name: '[[unread:watched-topics]]',
			url: 'recent/watched',
			selected: filter === 'watched',
			filter: 'watched'
		}];
 
		data.selectedFilter = data.filters.find(function (filter) {
			return filter && filter.selected;
		});
 
		var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
		data.pagination = pagination.create(page, pageCount, req.query);
 
		if (req.path.startsWith('/api/recent') || req.path.startsWith('/recent')) {
			data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[recent:title]]'}]);
		}
 
		data.querystring = cid ? ('?cid=' + validator.escape(String(cid))) : '';
		res.render('recent', data);
	});
};
 
module.exports = recentController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/search.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/search.js

Statements: 6.25% (2 / 32)      Branches: 0% (0 / 18)      Functions: 0% (0 / 2)      Lines: 6.25% (2 / 32)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76      2   2                                                                                                                                            
 
'use strict';
 
var async = require('async');
 
var meta = require('../meta');
var plugins = require('../plugins');
var search = require('../search');
var categories = require('../categories');
var pagination = require('../pagination');
var helpers = require('./helpers');
 
 
var searchController = {};
 
searchController.search = function (req, res, next) {
	if (!plugins.hasListeners('filter:search.query')) {
		return next();
	}
 
	if (!req.user && parseInt(meta.config.allowGuestSearching, 10) !== 1) {
		return helpers.notAllowed(req, res);
	}
 
	var page = Math.max(1, parseInt(req.query.page, 10)) || 1;
	if (req.query.categories && !Array.isArray(req.query.categories)) {
		req.query.categories = [req.query.categories];
	}
 
	var data = {
		query: req.query.term,
		searchIn: req.query.in || 'posts',
		postedBy: req.query.by,
		categories: req.query.categories,
		searchChildren: req.query.searchChildren,
		replies: req.query.replies,
		repliesFilter: req.query.repliesFilter,
		timeRange: req.query.timeRange,
		timeFilter: req.query.timeFilter,
		sortBy: req.query.sortBy,
		sortDirection: req.query.sortDirection,
		page: page,
		uid: req.uid,
		qs: req.query
	};
 
	async.parallel({
		categories: async.apply(categories.buildForSelect, req.uid),
		search: async.apply(search.search, data)
	}, function (err, results) {
		if (err) {
			return next(err);
		}
 
		var categoriesData = [
			{value: 'all', text: '[[unread:all_categories]]'},
			{value: 'watched', text: '[[category:watched-categories]]'}
		].concat(results.categories);
 
		var searchData = results.search;
		searchData.categories = categoriesData;
		searchData.categoriesCount = results.categories.length;
		searchData.pagination = pagination.create(page, searchData.pageCount, req.query);
		searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts';
		searchData.showAsTopics = req.query.showAs === 'topics';
		searchData.title = '[[global:header.search]]';
		searchData.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:search]]'}]);
		searchData.expandSearch = !req.query.term;
 
		res.render('search', searchData);
	});
};
 
module.exports = searchController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/sitemap.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/sitemap.js

Statements: 2.56% (1 / 39)      Branches: 0% (0 / 18)      Functions: 0% (0 / 9)      Lines: 2.56% (1 / 39)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69    2                                                                                                                                    
'use strict';
 
var sitemap = require('../sitemap');
var meta = require('../meta');
 
var sitemapController = {};
sitemapController.render = function (req, res, next) {
	sitemap.render(function (err, tplData) {
		if (err) {
			return next(err);
		}
 
		req.app.render('sitemap', tplData, function (err, xml) {
			if (err) {
				return next(err);
			}
			res.header('Content-Type', 'application/xml');
			res.send(xml);
		});
	});
};
 
sitemapController.getPages = function (req, res, next) {
	if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
		return next();
	}
 
	sitemap.getPages(function (err, xml) {
		if (err) {
			return next(err);
		}
		res.header('Content-Type', 'application/xml');
		res.send(xml);
	});
};
 
sitemapController.getCategories = function (req, res, next) {
	if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
		return next();
	}
 
	sitemap.getCategories(function (err, xml) {
		if (err) {
			return next(err);
		}
		res.header('Content-Type', 'application/xml');
		res.send(xml);
	});
};
 
sitemapController.getTopicPage = function (req, res, next) {
	if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
		return next();
	}
 
	sitemap.getTopicPage(parseInt(req.params[0], 10), function (err, xml) {
		if (err) {
			return next(err);
		} else if (!xml) {
			return next();
		}
 
		res.header('Content-Type', 'application/xml');
		res.send(xml);
	});
};
 
module.exports = sitemapController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/tags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/tags.js

Statements: 9.52% (4 / 42)      Branches: 0% (0 / 10)      Functions: 0% (0 / 9)      Lines: 9.52% (4 / 42)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98      2 2 2   2                                                                                                                                                                                    
"use strict";
 
 
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
 
var user = require('../user');
var topics = require('../topics');
var pagination = require('../pagination');
var helpers =  require('./helpers');
 
var tagsController = {};
 
tagsController.getTag = function (req, res, next) {
	var tag = validator.escape(String(req.params.tag));
	var page = parseInt(req.query.page, 10) || 1;
 
	var templateData = {
		topics: [],
		tag: tag,
		breadcrumbs: helpers.buildBreadcrumbs([{text: '[[tags:tags]]', url: '/tags'}, {text: tag}]),
		title: '[[pages:tag, ' + tag + ']]'
	};
	var settings;
	var topicCount = 0;
	async.waterfall([
		function (next) {
			user.getSettings(req.uid, next);
		},
		function (_settings, next) {
			settings = _settings;
			var start = Math.max(0, (page - 1) * settings.topicsPerPage);
			var stop = start + settings.topicsPerPage - 1;
			templateData.nextStart = stop + 1;
			async.parallel({
				topicCount: function (next) {
					topics.getTagTopicCount(tag, next);
				},
				tids: function (next) {
					topics.getTagTids(req.params.tag, start, stop, next);
				}
			}, next);
		},
		function (results, next) {
			if (Array.isArray(results.tids) && !results.tids.length) {
				return res.render('tag', templateData);
			}
			topicCount = results.topicCount;
			topics.getTopics(results.tids, req.uid, next);
		}
	], function (err, topics) {
		if (err) {
			return next(err);
		}
 
		res.locals.metaTags = [
			{
				name: 'title',
				content: tag
			},
			{
				property: 'og:title',
				content: tag
			},
			{
				property: 'og:url',
				content: nconf.get('url') + '/tags/' + tag
			}
		];
		templateData.topics = topics;
 
		var pageCount =	Math.max(1, Math.ceil(topicCount / settings.topicsPerPage));
		templateData.pagination = pagination.create(page, pageCount);
 
		res.render('tag', templateData);
	});
};
 
tagsController.getTags = function (req, res, next) {
	topics.getTags(0, 99, function (err, tags) {
		if (err) {
			return next(err);
		}
		tags = tags.filter(Boolean);
		var data = {
			tags: tags,
			nextStart: 100,
			breadcrumbs: helpers.buildBreadcrumbs([{text: '[[tags:tags]]'}]),
			title: '[[pages:tags]]'
		};
		res.render('tags', data);
	});
};
 
module.exports = tagsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/topics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/topics.js

Statements: 2.91% (5 / 172)      Branches: 0% (0 / 148)      Functions: 0% (0 / 22)      Lines: 2.91% (5 / 172)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368      2 2 2   2                                                                                                                                                                                                                                                                                             1                                                                                                                                                                                                                                                                                                                                                                                                                                                  
"use strict";
 
 
var async = require('async');
var S = require('string');
var nconf = require('nconf');
 
var user = require('../user');
var meta = require('../meta');
var topics = require('../topics');
var posts = require('../posts');
var privileges = require('../privileges');
var plugins = require('../plugins');
var helpers = require('./helpers');
var pagination = require('../pagination');
var utils = require('../../public/src/utils');
 
var topicsController = {};
 
topicsController.get = function (req, res, callback) {
	var tid = req.params.topic_id;
	var currentPage = parseInt(req.query.page, 10) || 1;
	var pageCount = 1;
	var userPrivileges;
	var settings;
 
	if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) {
		return callback();
	}
 
	async.waterfall([
		function (next) {
			async.parallel({
				privileges: function (next) {
					privileges.topics.get(tid, req.uid, next);
				},
				settings: function (next) {
					user.getSettings(req.uid, next);
				},
				topic: function (next) {
					topics.getTopicData(tid, next);
				}
			}, next);
		},
		function (results, next) {
			if (!results.topic) {
				return callback();
			}
 
			userPrivileges = results.privileges;
 
			if (!userPrivileges.read || !userPrivileges['topics:read'] || (parseInt(results.topic.deleted, 10) && !userPrivileges.view_deleted)) {
				return helpers.notAllowed(req, res);
			}
 
			if (!res.locals.isAPI && (!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) {
				var url = '/topic/' + results.topic.slug;
				if (req.params.post_index) {
					url += '/' + req.params.post_index;
				}
				if (currentPage > 1) {
					url += '?page=' + currentPage;
				}
				return helpers.redirect(res, url);
			}
 
			settings = results.settings;
			var postCount = parseInt(results.topic.postcount, 10);
			pageCount = Math.max(1, Math.ceil(postCount / settings.postsPerPage));
 
			if (utils.isNumber(req.params.post_index) && (req.params.post_index < 1 || req.params.post_index > postCount)) {
				return helpers.redirect(res, '/topic/' + req.params.topic_id + '/' + req.params.slug + (req.params.post_index > postCount ? '/' + postCount : ''));
			}
 
			if (settings.usePagination && (currentPage < 1 || currentPage > pageCount)) {
				return callback();
			}
 
			var set = 'tid:' + tid + ':posts';
			var reverse = false;
			// `sort` qs has priority over user setting
			var sort = req.query.sort || settings.topicPostSort;
			if (sort === 'newest_to_oldest') {
				reverse = true;
			} else if (sort === 'most_votes') {
				reverse = true;
				set = 'tid:' + tid + ':posts:votes';
			}
 
			var postIndex = 0;
 
			req.params.post_index = parseInt(req.params.post_index, 10) || 0;
			if (reverse && req.params.post_index === 1) {
				req.params.post_index = 0;
			}
			if (!settings.usePagination) {
				if (req.params.post_index !== 0) {
					currentPage = 1;
				}
				if (reverse) {
					postIndex = Math.max(0, postCount - (req.params.post_index || postCount) - Math.ceil(settings.postsPerPage / 2));
				} else {
					postIndex = Math.max(0, (req.params.post_index || 1) - Math.ceil(settings.postsPerPage / 2));
				}
			} else if (!req.query.page) {
				var index;
				if (reverse) {
					index = Math.max(0, postCount - (req.params.post_index || postCount) + 2);
				} else {
					index = Math.max(0, req.params.post_index) || 0;
				}
 
				currentPage = Math.max(1, Math.ceil(index / settings.postsPerPage));
			}
 
			var start = (currentPage - 1) * settings.postsPerPage + postIndex;
			var stop = start + settings.postsPerPage - 1;
 
			topics.getTopicWithPosts(results.topic, set, req.uid, start, stop, reverse, next);
		},
		function (topicData, next) {
			if (topicData.category.disabled) {
				return callback();
			}
 
			topics.modifyPostsByPrivilege(topicData, userPrivileges);
 
			plugins.fireHook('filter:controllers.topic.get', {topicData: topicData, uid: req.uid}, next);
		},
		function (data, next) {
 
			var breadcrumbs = [
				{
					text: data.topicData.category.name,
					url: nconf.get('relative_path') + '/category/' + data.topicData.category.slug
				},
				{
					text: data.topicData.title
				}
			];
 
			helpers.buildCategoryBreadcrumbs(data.topicData.category.parentCid, function (err, crumbs) {
				if (err) {
					return next(err);
				}
				data.topicData.breadcrumbs = crumbs.concat(breadcrumbs);
				next(null, data.topicData);
			});
		},
		function (topicData, next) {
			function findPost(index) {
				for(var i = 0; i < topicData.posts.length; ++i) {
					if (parseInt(topicData.posts[i].index, 10) === parseInt(index, 10)) {
						return topicData.posts[i];
					}
				}
			}
			var description = '';
			var postAtIndex = findPost(Math.max(0, req.params.post_index - 1));
 
			if (postAtIndex && postAtIndex.content) {
				description = S(postAtIndex.content).decodeHTMLEntities().stripTags().s;
			}
 
			if (description.length > 255) {
				description = description.substr(0, 255) + '...';
			}
 
			var ogImageUrl = '';
			if (topicData.thumb) {
				ogImageUrl = topicData.thumb;
			} else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture) {
				ogImageUrl = postAtIndex.user.picture;
			} else if (meta.config['og:image']) {
				ogImageUrl = meta.config['og:image'];
			} else if (meta.config['brand:logo']) {
				ogImageUrl = meta.config['brand:logo'];
			} else {
				ogImageUrl = '/logo.png';
			}
 
			if (typeof ogImageUrl === 'string' && ogImageUrl.indexOf('http') === -1) {
				ogImageUrl = nconf.get('url') + ogImageUrl;
			}
 
			description = description.replace(/\n/g, ' ');
 
			res.locals.metaTags = [
				{
					name: "title",
					content: topicData.titleRaw
				},
				{
					name: "description",
					content: description
				},
				{
					property: 'og:title',
					content: topicData.titleRaw
				},
				{
					property: 'og:description',
					content: description
				},
				{
					property: "og:type",
					content: 'article'
				},
				{
					property: "og:url",
					content: nconf.get('url') + '/topic/' + topicData.slug + (req.params.post_index ? ('/' + req.params.post_index) : ''),
					noEscape: true
				},
				{
					property: 'og:image',
					content: ogImageUrl,
					noEscape: true
				},
				{
					property: "og:image:url",
					content: ogImageUrl,
					noEscape: true
				},
				{
					property: "article:published_time",
					content: utils.toISOString(topicData.timestamp)
				},
				{
					property: 'article:modified_time',
					content: utils.toISOString(topicData.lastposttime)
				},
				{
					property: 'article:section',
					content: topicData.category ? topicData.category.name : ''
				}
			];
 
			res.locals.linkTags = [
				{
					rel: 'alternate',
					type: 'application/rss+xml',
					href: nconf.get('url') + '/topic/' + tid + '.rss'
				}
			];
 
			if (topicData.category) {
				res.locals.linkTags.push({
					rel: 'up',
					href: nconf.get('url') + '/category/' + topicData.category.slug
				});
			}
 
			next(null, topicData);
		}
	], function (err, data) {
		if (err) {
			return callback(err);
		}
 
		data.privileges = userPrivileges;
		data.topicStaleDays = parseInt(meta.config.topicStaleDays, 10) || 60;
		data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
		data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
		data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
		data.bookmarkThreshold = parseInt(meta.config.bookmarkThreshold, 10) || 5;
		data.postEditDuration = parseInt(meta.config.postEditDuration, 10) || 0;
		data.postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10) || 0;
		data.scrollToMyPost = settings.scrollToMyPost;
		data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss';
		data.pagination = pagination.create(currentPage, pageCount, req.query);
		data.pagination.rel.forEach(function (rel) {
			rel.href = nconf.get('url') + '/topic/' + data.slug + rel.href;
			res.locals.linkTags.push(rel);
		});
 
		req.session.tids_viewed = req.session.tids_viewed || {};
		if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < Date.now() - 3600000) {
			topics.increaseViewCount(tid);
			req.session.tids_viewed[tid] = Date.now();
		}
 
		if (req.uid) {
			topics.markAsRead([tid], req.uid, function (err, markedRead) {
				if (err) {
					return callback(err);
				}
				if (markedRead) {
					topics.pushUnreadCount(req.uid);
					topics.markTopicNotificationsRead([tid], req.uid);
				}
			});
		}
 
		res.render('topic', data);
	});
};
 
topicsController.teaser = function (req, res, next) {
	var tid = req.params.topic_id;
 
	if (!utils.isNumber(tid)) {
		return next(new Error('[[error:invalid-tid]]'));
	}
 
	async.waterfall([
		function (next) {
			privileges.topics.can('read', tid, req.uid, next);
		},
		function (canRead, next) {
			if (!canRead) {
				return res.status(403).json('[[error:no-privileges]]');
			}
			topics.getLatestUndeletedPid(tid, next);
		},
		function (pid, next) {
			if (!pid) {
				return res.status(404).json('not-found');
			}
			posts.getPostSummaryByPids([pid], req.uid, {stripTags: false}, next);
		}
	], function (err, posts) {
		if (err) {
			return next(err);
		}
 
		if (!Array.isArray(posts) || !posts.length) {
			return res.status(404).json('not-found');
		}
		res.json(posts[0]);
	});
};
 
topicsController.pagination = function (req, res, callback) {
	var tid = req.params.topic_id;
	var currentPage = parseInt(req.query.page, 10) || 1;
 
	if (!utils.isNumber(tid)) {
		return callback();
	}
 
	async.parallel({
		privileges: async.apply(privileges.topics.get, tid, req.uid),
		settings: async.apply(user.getSettings, req.uid),
		topic: async.apply(topics.getTopicData, tid)
	}, function (err, results) {
		if (err || !results.topic) {
			return callback(err);
		}
 
		if (!results.privileges.read || (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted)) {
			return helpers.notAllowed(req, res);
		}
 
		var postCount = parseInt(results.topic.postcount, 10);
		var pageCount = Math.max(1, Math.ceil((postCount - 1) / results.settings.postsPerPage));
 
		var paginationData = pagination.create(currentPage, pageCount);
		paginationData.rel.forEach(function (rel) {
			rel.href = nconf.get('url') + '/topic/' + results.topic.slug + rel.href;
		});
 
		res.json(paginationData);
	});
};
 
module.exports = topicsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/unread.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/unread.js

Statements: 9.62% (5 / 52)      Branches: 0% (0 / 27)      Functions: 0% (0 / 9)      Lines: 9.62% (5 / 52)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111      2 2 2   2 2                                                                                                                                                                                                            
 
'use strict';
 
var async = require('async');
var querystring = require('querystring');
var validator = require('validator');
 
var pagination = require('../pagination');
var user = require('../user');
var topics = require('../topics');
var helpers = require('./helpers');
 
var unreadController = {};
 
var validFilter = {'': true, 'new': true, 'watched': true};
 
unreadController.get = function (req, res, next) {
	var page = parseInt(req.query.page, 10) || 1;
	var results;
	var cid = req.query.cid;
	var filter = req.params.filter || '';
 
	if (!validFilter[filter]) {
		return next();
	}
	var settings;
	async.waterfall([
		function (next) {
			async.parallel({
				watchedCategories: function (next) {
					helpers.getWatchedCategories(req.uid, cid, next);
				},
				settings: function (next) {
					user.getSettings(req.uid, next);
				}
			}, next);
		},
		function (_results, next) {
			results = _results;
			settings = results.settings;
			var start = Math.max(0, (page - 1) * settings.topicsPerPage);
			var stop = start + settings.topicsPerPage - 1;
			topics.getUnreadTopics(cid, req.uid, start, stop, filter, next);
		}
	], function (err, data) {
		if (err) {
			return next(err);
		}
 
		data.pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
		data.pagination = pagination.create(page, data.pageCount, req.query);
 
		if (settings.usePagination && (page < 1 || page > data.pageCount)) {
			req.query.page = Math.max(1, Math.min(data.pageCount, page));
			return helpers.redirect(res, '/unread?' + querystring.stringify(req.query));
		}
 
		data.categories = results.watchedCategories.categories;
		data.selectedCategory = results.watchedCategories.selectedCategory;
 
		if (req.path.startsWith('/api/unread') || req.path.startsWith('/unread')) {
			data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[unread:title]]'}]);
		}
 
		data.title = '[[pages:unread]]';
		data.filters = [{
			name: '[[unread:all-topics]]',
			url: 'unread',
			selected: filter === '',
			filter: ''
		}, {
			name: '[[unread:new-topics]]',
			url: 'unread/new',
			selected: filter === 'new',
			filter: 'new'
		}, {
			name: '[[unread:watched-topics]]',
			url: 'unread/watched',
			selected: filter === 'watched',
			filter: 'watched'
		}];
 
		data.selectedFilter = data.filters.find(function (filter) {
			return filter && filter.selected;
		});
 
		data.querystring = cid ? ('?cid=' + validator.escape(String(cid))) : '';
 
		res.render('unread', data);
	});
};
 
unreadController.unreadTotal = function (req, res, next) {
	var filter = req.params.filter || '';
 
	if (!validFilter[filter]) {
		return next();
	}
 
	topics.getTotalUnread(req.uid, filter, function (err, data) {
		if (err) {
			return next(err);
		}
 
		res.json(data);
	});
};
 
module.exports = unreadController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/uploads.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/uploads.js

Statements: 11.48% (14 / 122)      Branches: 0% (0 / 66)      Functions: 0% (0 / 28)      Lines: 11.48% (14 / 122)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250    4 4 4 4 4 4 4   4                                                                                 1                                                     1                                 1                                                                                                                                                                                 1                                                     1                                             1                              
"use strict";
 
var fs = require('fs');
var path = require('path');
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
var winston = require('winston');
var mime = require('mime');
 
var meta = require('../meta');
var file = require('../file');
var plugins = require('../plugins');
var image = require('../image');
var privileges = require('../privileges');
 
var uploadsController = {};
 
uploadsController.upload = function (req, res, filesIterator) {
	var files = req.files.files;
 
	if (!Array.isArray(files)) {
		return res.status(500).json('invalid files');
	}
 
	if (Array.isArray(files[0])) {
		files = files[0];
	}
 
	async.map(files, filesIterator, function (err, images) {
		deleteTempFiles(files);
 
		if (err) {
			return res.status(500).send(err.message);
		}
 
		res.status(200).send(images);
	});
};
 
uploadsController.uploadPost = function (req, res, next) {
	uploadsController.upload(req, res, function (uploadedFile, next) {
		var isImage = uploadedFile.type.match(/image./);
		if (isImage) {
			uploadAsImage(req, uploadedFile, next);
		} else {
			uploadAsFile(req, uploadedFile, next);
		}
	}, next);
};
 
function uploadAsImage(req, uploadedFile, callback) {
	async.waterfall([
		function (next) {
			privileges.categories.can('upload:post:image', req.body.cid, req.uid, next);
		},
		function (canUpload, next) {
			if (!canUpload) {
				return next(new Error('[[error:no-privileges]]'));
			}
			if (plugins.hasListeners('filter:uploadImage')) {
				return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, callback);
			}
			file.isFileTypeAllowed(uploadedFile.path, next);
		},
		function (next) {
			uploadFile(req.uid, uploadedFile, next);
		},
		function (fileObj, next) {
			if (parseInt(meta.config.maximumImageWidth, 10) === 0) {
				return next(null, fileObj);
			}
 
			resizeImage(fileObj, next);
		}
	], callback);
}
 
function uploadAsFile(req, uploadedFile, callback) {
	async.waterfall([
		function (next) {
			privileges.categories.can('upload:post:file', req.body.cid, req.uid, next);
		},
		function (canUpload, next) {
			if (!canUpload) {
				return next(new Error('[[error:no-privileges]]'));
			}
			if (parseInt(meta.config.allowFileUploads, 10) !== 1) {
				return next(new Error('[[error:uploads-are-disabled]]'));
			}
			uploadFile(req.uid, uploadedFile, next);
		}
	], callback);
}
 
function resizeImage(fileObj, callback) {
	async.waterfall([
		function (next) {
			image.size(fileObj.path, next);
		},
		function (imageData, next) {
			if (imageData.width < (parseInt(meta.config.maximumImageWidth, 10) || 760)) {
				return callback(null, fileObj);
			}
 
			var dirname = path.dirname(fileObj.path);
			var extname = path.extname(fileObj.path);
			var basename = path.basename(fileObj.path, extname);
 
			image.resizeImage({
				path: fileObj.path,
				target: path.join(dirname, basename + '-resized' + extname),
				extension: extname,
				width: parseInt(meta.config.maximumImageWidth, 10) || 760
			}, next);
		},
		function (next) {
 
			// Return the resized version to the composer/postData
			var dirname = path.dirname(fileObj.url);
			var extname = path.extname(fileObj.url);
			var basename = path.basename(fileObj.url, extname);
 
			fileObj.url = path.join(dirname, basename + '-resized' + extname);
 
			next(null, fileObj);
		}
	], callback);
}
 
uploadsController.uploadThumb = function (req, res, next) {
	if (parseInt(meta.config.allowTopicsThumbnail, 10) !== 1) {
		deleteTempFiles(req.files.files);
		return next(new Error('[[error:topic-thumbnails-are-disabled]]'));
	}
 
	uploadsController.upload(req, res, function (uploadedFile, next) {
		file.isFileTypeAllowed(uploadedFile.path, function (err) {
			if (err) {
				return next(err);
			}
 
			if (!uploadedFile.type.match(/image./)) {
				return next(new Error('[[error:invalid-file]]'));
			}
 
			var size = parseInt(meta.config.topicThumbSize, 10) || 120;
			image.resizeImage({
				path: uploadedFile.path,
				extension: path.extname(uploadedFile.name),
				width: size,
				height: size
			}, function (err) {
				if (err) {
					return next(err);
				}
 
				if (plugins.hasListeners('filter:uploadImage')) {
					return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, next);
				}
 
				uploadFile(req.uid, uploadedFile, next);
			});
		});
	}, next);
};
 
uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) {
	if (plugins.hasListeners('filter:uploadImage')) {
		return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: uid}, callback);
	}
 
	if (plugins.hasListeners('filter:uploadFile')) {
		return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback);
	}
 
	file.isFileTypeAllowed(uploadedFile.path, function (err) {
		if (err) {
			return callback(err);
		}
		saveFileToLocal(uploadedFile, callback);
	});
};
 
function uploadFile(uid, uploadedFile, callback) {
	if (plugins.hasListeners('filter:uploadFile')) {
		return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback);
	}
 
	if (!uploadedFile) {
		return callback(new Error('[[error:invalid-file]]'));
	}
 
	if (uploadedFile.size > parseInt(meta.config.maximumFileSize, 10) * 1024) {
		return callback(new Error('[[error:file-too-big, ' + meta.config.maximumFileSize + ']]'));
	}
 
	if (meta.config.hasOwnProperty('allowedFileExtensions')) {
		var allowed = file.allowedExtensions();
		var extension = path.extname(uploadedFile.name);
		if (!extension) {
			extension = '.' + mime.extension(uploadedFile.type);
		}
		if (allowed.length > 0 && allowed.indexOf(extension) === -1) {
			return callback(new Error('[[error:invalid-file-type, ' + allowed.join('&#44; ') + ']]'));
		}
	}
 
	saveFileToLocal(uploadedFile, callback);
}
 
function saveFileToLocal(uploadedFile, callback) {
	var extension = path.extname(uploadedFile.name);
	if (!extension && uploadedFile.type) {
		extension = '.' + mime.extension(uploadedFile.type);
	}
 
	var filename = uploadedFile.name || 'upload';
 
	filename = Date.now() + '-' + validator.escape(filename.replace(extension, '')).substr(0, 255) + extension;
 
	file.saveFileToLocal(filename, 'files', uploadedFile.path, function (err, upload) {
		if (err) {
			return callback(err);
		}
 
		callback(null, {
			url: nconf.get('relative_path') + upload.url,
			path: upload.path,
			name: uploadedFile.name
		});
	});
}
 
function deleteTempFiles(files) {
	async.each(files, function (file, next) {
		fs.unlink(file.path, function (err) {
			if (err) {
				winston.error(err);
			}
			next();
		});
	});
}
 
 
 
module.exports = uploadsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/users.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/users.js

Statements: 2.56% (3 / 117)      Branches: 0% (0 / 64)      Functions: 0% (0 / 30)      Lines: 2.56% (3 / 117)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249    2 2                                                                                                                                                                                                                                                                                                                                                                                                                                                             1                                            
"use strict";
 
var async = require('async');
var user = require('../user');
var meta = require('../meta');
 
var pagination = require('../pagination');
var db = require('../database');
var helpers = require('./helpers');
 
 
var usersController = {};
 
 
usersController.index = function (req, res, next) {
	var section = req.query.section || 'joindate';
	var sectionToController = {
		joindate: usersController.getUsersSortedByJoinDate,
		online: usersController.getOnlineUsers,
		'sort-posts': usersController.getUsersSortedByPosts,
		'sort-reputation': usersController.getUsersSortedByReputation,
		banned: usersController.getBannedUsers,
		flagged: usersController.getFlaggedUsers
	};
 
	if (req.query.term) {
		usersController.search(req, res, next);
	} else if (sectionToController[section]) {
		sectionToController[section](req, res, next);
	} else {
		usersController.getUsersSortedByJoinDate(req, res, next);
	}
};
 
usersController.search = function (req, res, next) {
	async.parallel({
		search: function (next) {
			user.search({
				query: req.query.term,
				searchBy: req.query.searchBy || 'username',
				page: req.query.page || 1,
				sortBy: req.query.sortBy,
				onlineOnly: req.query.onlineOnly === 'true',
				bannedOnly: req.query.bannedOnly === 'true',
				flaggedOnly: req.query.flaggedOnly === 'true'
			}, next);
		},
		isAdminOrGlobalMod: function (next) {
			user.isAdminOrGlobalMod(req.uid, next);
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
 
		var section = req.query.section || 'joindate';
 
		results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod;
		results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query);
		results.search['section_' + section] = true;
		render(req, res, results.search, next);
	});
};
 
usersController.getOnlineUsers = function (req, res, next) {
	async.parallel({
		users: function (next) {
			usersController.getUsers('users:online', req.uid, req.query, next);
		},
		guests: function (next) {
			require('../socket.io/admin/rooms').getTotalGuestCount(next);
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
		var userData = results.users;
		var hiddenCount = 0;
		if (!userData.isAdminOrGlobalMod) {
			userData.users = userData.users.filter(function (user) {
				if (user && user.status === 'offline') {
					hiddenCount ++;
				}
				return user && user.status !== 'offline';
			});
		}
 
		userData.anonymousUserCount = results.guests + hiddenCount;
 
		render(req, res, userData, next);
	});
};
 
usersController.getUsersSortedByPosts = function (req, res, next) {
	usersController.renderUsersPage('users:postcount', req, res, next);
};
 
usersController.getUsersSortedByReputation = function (req, res, next) {
	if (parseInt(meta.config['reputation:disabled'], 10) === 1) {
		return next();
	}
	usersController.renderUsersPage('users:reputation', req, res, next);
};
 
usersController.getUsersSortedByJoinDate = function (req, res, next) {
	usersController.renderUsersPage('users:joindate', req, res, next);
};
 
usersController.getBannedUsers = function (req, res, next) {
	usersController.getUsers('users:banned', req.uid, req.query, function (err, userData) {
		if (err) {
			return next(err);
		}
 
		if (!userData.isAdminOrGlobalMod) {
			return next();
		}
 
		render(req, res, userData, next);
	});
};
 
usersController.getFlaggedUsers = function (req, res, next) {
	usersController.getUsers('users:flags', req.uid, req.query, function (err, userData) {
		if (err) {
			return next(err);
		}
 
		if (!userData.isAdminOrGlobalMod) {
			return next();
		}
 
		render(req, res, userData, next);
	});
};
 
usersController.renderUsersPage = function (set, req, res, next) {
	usersController.getUsers(set, req.uid, req.query, function (err, userData) {
		if (err) {
			return next(err);
		}
 
		render(req, res, userData, next);
	});
};
 
usersController.getUsers = function (set, uid, query, callback) {
	var setToData = {
		'users:postcount': {title: '[[pages:users/sort-posts]]', crumb: '[[users:top_posters]]'},
		'users:reputation': {title: '[[pages:users/sort-reputation]]', crumb: '[[users:most_reputation]]'},
		'users:joindate': {title: '[[pages:users/latest]]', crumb: '[[global:users]]'},
		'users:online': {title: '[[pages:users/online]]', crumb: '[[global:online]]'},
		'users:banned': {title: '[[pages:users/banned]]', crumb: '[[user:banned]]'},
		'users:flags': {title: '[[pages:users/most-flags]]', crumb: '[[users:most_flags]]'},
	};
 
	if (!setToData[set]) {
		setToData[set] = {title: '', crumb: ''};
	}
 
	var breadcrumbs = [{text: setToData[set].crumb}];
 
	if (set !== 'users:joindate') {
		breadcrumbs.unshift({text: '[[global:users]]', url: '/users'});
	}
 
	var page = parseInt(query.page, 10) || 1;
	var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 50;
	var start = Math.max(0, page - 1) * resultsPerPage;
	var stop = start + resultsPerPage - 1;
 
	async.parallel({
		isAdminOrGlobalMod: function (next) {
			user.isAdminOrGlobalMod(uid, next);
		},
		usersData: function (next) {
			usersController.getUsersAndCount(set, uid, start, stop, next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		var pageCount = Math.ceil(results.usersData.count / resultsPerPage);
		var userData = {
			users: results.usersData.users,
			pagination: pagination.create(page, pageCount, query),
			userCount: results.usersData.count,
			title: setToData[set].title || '[[pages:users/latest]]',
			breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs),
			isAdminOrGlobalMod: results.isAdminOrGlobalMod
		};
		userData['section_' + (query.section || 'joindate')] = true;
		callback(null, userData);
	});
};
 
usersController.getUsersAndCount = function (set, uid, start, stop, callback) {
	async.parallel({
		users: function (next) {
			user.getUsersFromSet(set, uid, start, stop, next);
		},
		count: function (next) {
			if (set === 'users:online') {
				var now = Date.now();
				db.sortedSetCount('users:online', now - 300000, '+inf', next);
			} else if (set === 'users:banned') {
				db.sortedSetCard('users:banned', next);
			} else if (set === 'users:flags') {
				db.sortedSetCard('users:flags', next);
			} else {
				db.getObjectField('global', 'userCount', next);
			}
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
		results.users = results.users.filter(function (user) {
			return user && parseInt(user.uid, 10);
		});
 
		callback(null, results);
	});
};
 
function render(req, res, data, next) {
	var registrationType = meta.config.registrationType;
 
	data.maximumInvites = meta.config.maximumInvites;
	data.inviteOnly = registrationType === 'invite-only' || registrationType === 'admin-invite-only';
	data.adminInviteOnly = registrationType === 'admin-invite-only';
	data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
 
	user.getInvitesNumber(req.uid, function (err, numInvites) {
		if (err) {
			return next(err);
		}
 
		res.append('X-Total-Count', data.userCount);
		data.invites = numInvites;
 
		res.render('users', data);
	});
}
 
module.exports = usersController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/

Statements: 6.33% (29 / 458)      Branches: 0% (0 / 175)      Functions: 0% (0 / 108)      Lines: 6.33% (29 / 458)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/
File Statements Branches Functions Lines
chats.js 3.51% (2 / 57) 0% (0 / 37) 0% (0 / 9) 3.51% (2 / 57)
edit.js 8.24% (7 / 85) 0% (0 / 40) 0% (0 / 22) 8.24% (7 / 85)
follow.js 9.09% (3 / 33) 0% (0 / 10) 0% (0 / 6) 9.09% (3 / 33)
groups.js 7.14% (2 / 28) 0% (0 / 4) 0% (0 / 8) 7.14% (2 / 28)
info.js 8.7% (2 / 23) 0% (0 / 4) 0% (0 / 4) 8.7% (2 / 23)
notifications.js 12.5% (1 / 8) 0% (0 / 2) 0% (0 / 2) 12.5% (1 / 8)
posts.js 4.92% (3 / 61) 0% (0 / 12) 0% (0 / 15) 4.92% (3 / 61)
profile.js 6.15% (4 / 65) 0% (0 / 39) 0% (0 / 11) 6.15% (4 / 65)
session.js 6.67% (2 / 30) 0% (0 / 15) 0% (0 / 8) 6.67% (2 / 30)
settings.js 4.41% (3 / 68) 0% (0 / 12) 0% (0 / 23) 4.41% (3 / 68)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/chats.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/chats.js

Statements: 3.51% (2 / 57)      Branches: 0% (0 / 37)      Functions: 0% (0 / 9)      Lines: 3.51% (2 / 57)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119    2   2                                                                                                                                                                                                                                    
'use strict';
 
var async = require('async');
 
var messaging = require('../../messaging');
var meta = require('../../meta');
var user = require('../../user');
var helpers = require('../helpers');
 
var chatsController = {};
 
chatsController.get = function (req, res, callback) {
	if (parseInt(meta.config.disableChat, 10) === 1) {
		return callback();
	}
	var uid;
	var username;
	var recentChats;
 
	async.waterfall([
		function (next) {
			async.parallel({
				uid: async.apply(user.getUidByUserslug, req.params.userslug),
				username: async.apply(user.getUsernameByUserslug, req.params.userslug)
			}, next);
		},
		function (results, next) {
			uid = results.uid;
			username = results.username;
			if (!uid) {
				return callback();
			}
			messaging.getRecentChats(req.uid, uid, 0, 19, next);
		},
		function (_recentChats, next) {
			recentChats = _recentChats;
			if (!recentChats) {
				return callback();
			}
			if (!req.params.roomid) {
				return res.render('chats', {
					rooms: recentChats.rooms,
					uid: uid,
					userslug: req.params.userslug,
					nextStart: recentChats.nextStart,
					allowed: true,
					title: '[[pages:chats]]',
					breadcrumbs: helpers.buildBreadcrumbs([{text: username, url: '/user/' + req.params.userslug}, {text: '[[pages:chats]]'}])
				});
			}
			messaging.isUserInRoom(req.uid, req.params.roomid, next);
		},
		function (inRoom, next) {
			if (!inRoom) {
				return callback();
			}
			async.parallel({
				users: async.apply(messaging.getUsersInRoom, req.params.roomid, 0, -1),
				canReply: async.apply(messaging.canReply, req.params.roomid, req.uid),
				room: async.apply(messaging.getRoomData, req.params.roomid),
				messages: async.apply(messaging.getMessages, {
					callerUid: req.uid,
					uid: uid,
					roomId: req.params.roomid,
					isNew: false
				})
			}, next);
		}
	], function (err, data) {
		if (err) {
			return callback(err);
		}
		var room = data.room;
		room.messages = data.messages;
 
		room.isOwner = parseInt(room.owner, 10) === parseInt(req.uid, 10);
		room.users = data.users.filter(function (user) {
			return user && parseInt(user.uid, 10) && parseInt(user.uid, 10) !== req.uid;
		});
 
		room.canReply = data.canReply;
		room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2;
		room.rooms = recentChats.rooms;
		room.uid = uid;
		room.userslug = req.params.userslug;
		room.nextStart = recentChats.nextStart;
		room.usernames = messaging.generateUsernames(room.users, req.uid);
		room.title = room.roomName || room.usernames || '[[pages:chats]]';
		room.breadcrumbs = helpers.buildBreadcrumbs([
			{text: username, url: '/user/' + req.params.userslug},
			{text: '[[pages:chats]]', url: '/user/' + req.params.userslug + '/chats'},
			{text: room.roomName || room.usernames || '[[pages:chats]]'}
		]);
		room.maximumUsersInChatRoom = parseInt(meta.config.maximumUsersInChatRoom, 10) || 0;
		room.maximumChatMessageLength = parseInt(meta.config.maximumChatMessageLength, 10) || 1000;
		room.showUserInput = !room.maximumUsersInChatRoom || room.maximumUsersInChatRoom > 2;
 
		res.render('chats', room);
	});
};
 
chatsController.redirectToChat = function (req, res, next) {
	var roomid = parseInt(req.params.roomid, 10);
	if (!req.uid) {
		return next();
	}
	user.getUserField(req.uid, 'userslug', function (err, userslug) {
		if (err || !userslug) {
			return next(err);
		}
 
		helpers.redirect(res, '/user/' + userslug + '/chats' + (roomid ? '/' + roomid : ''));
	});
};
 
 
 
module.exports = chatsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/edit.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/edit.js

Statements: 8.24% (7 / 85)      Branches: 0% (0 / 40)      Functions: 0% (0 / 22)      Lines: 8.24% (7 / 85)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162    2 2 2 2   2                                                                                                               1                                               1                                                                                                                                                    
'use strict';
 
var async = require('async');
var fs = require('fs');
var nconf = require('nconf');
var winston = require('winston');
 
var db = require('../../database');
var user = require('../../user');
var meta = require('../../meta');
var plugins = require('../../plugins');
var helpers = require('../helpers');
var groups = require('../../groups');
var accountHelpers = require('./helpers');
var privileges = require('../../privileges');
 
var editController = {};
 
editController.get = function (req, res, callback) {
	accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, function (err, userData) {
		if (err || !userData) {
			return callback(err);
		}
 
		userData.maximumSignatureLength = parseInt(meta.config.maximumSignatureLength, 10) || 255;
		userData.maximumAboutMeLength = parseInt(meta.config.maximumAboutMeLength, 10) || 1000;
		userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10);
		userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads) === 1;
		userData.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1;
 
		userData.groups = userData.groups.filter(function (group) {
			return group && group.userTitleEnabled && !groups.isPrivilegeGroup(group.name) && group.name !== 'registered-users';
		});
		userData.groups.forEach(function (group) {
			group.selected = group.name === userData.groupTitle;
		});
 
		userData.title = '[[pages:account/edit, ' + userData.username + ']]';
		userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]);
		userData.editButtons = [];
 
		plugins.fireHook('filter:user.account.edit', userData, function (err, userData) {
			if (err) {
				return callback(err);
			}
 
			res.render('account/edit', userData);
		});
	});
};
 
editController.password = function (req, res, next) {
	renderRoute('password', req, res, next);
};
 
editController.username = function (req, res, next) {
	renderRoute('username', req, res, next);
};
 
editController.email = function (req, res, next) {
	renderRoute('email', req, res, next);
};
 
function renderRoute(name, req, res, next) {
	getUserData(req, next, function (err, userData) {
		if (err || !userData) {
			return next(err);
		}
		if ((name === 'username' && userData['username:disableEdit']) || (name === 'email' && userData['email:disableEdit'])) {
			return next();
		}
 
		if (name === 'password') {
			userData.minimumPasswordLength = parseInt(meta.config.minimumPasswordLength, 10);
		}
 
		userData.title = '[[pages:account/edit/' + name + ', ' + userData.username + ']]';
		userData.breadcrumbs = helpers.buildBreadcrumbs([
			{text: userData.username, url: '/user/' + userData.userslug},
			{text: '[[user:edit]]', url: '/user/' + userData.userslug + '/edit'},
			{text: '[[user:' + name + ']]'}
		]);
 
		res.render('account/edit/' + name, userData);
	});
}
 
function getUserData(req, next, callback) {
	var userData;
	async.waterfall([
		function (next) {
			accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next);
		},
		function (data, next) {
			userData = data;
			if (!userData) {
				return callback();
			}
			db.getObjectField('user:' + userData.uid, 'password', next);
		}
	], function (err, password) {
		if (err) {
			return callback(err);
		}
 
		userData.hasPassword = !!password;
		callback(null, userData);
	});
}
 
editController.uploadPicture = function (req, res, next) {
	var userPhoto = req.files.files[0];
 
	var updateUid;
 
	async.waterfall([
		function (next) {
			user.getUidByUserslug(req.params.userslug, next);
		},
		function (uid, next) {
			updateUid = uid;
 
			privileges.users.canEdit(req.uid, uid, next);
		},
		function (isAllowed, next) {
			if (!isAllowed) {
				return helpers.notAllowed(req, res);
			}
 
			user.uploadPicture(updateUid, userPhoto, next);
		}
	], function (err, image) {
		fs.unlink(userPhoto.path, function (err) {
			if (err) {
				winston.warn('[user/picture] Unable to delete picture ' + userPhoto.path, err);
			}
		});
		if (err) {
			return next(err);
		}
 
		res.json([{name: userPhoto.name, url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
	});
};
 
editController.uploadCoverPicture = function (req, res, next) {
	var params = JSON.parse(req.body.params);
 
	user.updateCoverPicture({
		file: req.files.files[0],
		uid: params.uid
	}, function (err, image) {
		if (err) {
			return next(err);
		}
 
		res.json([{ url: image.url }]);
	});
};
 
module.exports = editController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/follow.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/follow.js

Statements: 9.09% (3 / 33)      Branches: 0% (0 / 10)      Functions: 0% (0 / 6)      Lines: 9.09% (3 / 33)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57    2   2                             1                                                                          
'use strict';
 
var async = require('async');
 
var user = require('../../user');
var helpers = require('../helpers');
var accountHelpers = require('./helpers');
var pagination = require('../../pagination');
 
var followController = {};
 
followController.getFollowing = function (req, res, next) {
	getFollow('account/following', 'following', req, res, next);
};
 
followController.getFollowers = function (req, res, next) {
	getFollow('account/followers', 'followers', req, res, next);
};
 
function getFollow(tpl, name, req, res, callback) {
	var userData;
 
	var page = parseInt(req.query.page, 10) || 1;
	var resultsPerPage = 50;
	var start = Math.max(0, page - 1) * resultsPerPage;
	var stop = start + resultsPerPage - 1;
 
	async.waterfall([
		function (next) {
			accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next);
		},
		function (data, next) {
			userData = data;
			if (!userData) {
				return callback();
			}
			var method = name === 'following' ? 'getFollowing' : 'getFollowers';
			user[method](userData.uid, start, stop, next);
		}
	], function (err, users) {
		if (err) {
			return callback(err);
		}
 
		userData.users = users;
		userData.title = '[[pages:' + tpl + ', ' + userData.username + ']]';
		var count = name === 'following' ? userData.followingCount : userData.followerCount;
		var pageCount = Math.ceil(count / resultsPerPage);
		userData.pagination = pagination.create(page, pageCount);
		userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:' + name + ']]'}]);
 
		res.render(tpl, userData);
	});
}
 
module.exports = followController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/groups.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/groups.js

Statements: 7.14% (2 / 28)      Branches: 0% (0 / 4)      Functions: 0% (0 / 8)      Lines: 7.14% (2 / 28)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55      2   2                                                                                                  
'use strict';
 
 
var async = require('async');
 
var groups = require('../../groups');
var helpers = require('../helpers');
var accountHelpers = require('./helpers');
 
var groupsController = {};
 
 
groupsController.get = function (req, res, callback) {
	var userData;
	var groupsData;
	async.waterfall([
		function (next) {
			accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next);
		},
		function (_userData, next) {
			userData = _userData;
			if (!userData) {
				return callback();
			}
 
			groups.getUserGroups([userData.uid], next);
		},
		function (_groupsData, next) {
			groupsData = _groupsData[0];
			var groupNames = groupsData.filter(Boolean).map(function (group) {
				return group.name;
			});
 
			groups.getMemberUsers(groupNames, 0, 3, next);
		},
		function (members, next) {
			groupsData.forEach(function (group, index) {
				group.members = members[index];
			});
			next();
		}
	], function (err) {
		if (err) {
			return callback(err);
		}
 
		userData.groups = groupsData;
		userData.title = '[[pages:account/groups, ' + userData.username + ']]';
		userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[global:header.groups]]'}]);
		res.render('account/groups', userData);
	});
};
 
module.exports = groupsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/info.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/info.js

Statements: 8.7% (2 / 23)      Branches: 0% (0 / 4)      Functions: 0% (0 / 4)      Lines: 8.7% (2 / 23)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46    2   2                                                                                  
'use strict';
 
var async = require('async');
 
var user = require('../../user');
var helpers = require('../helpers');
var accountHelpers = require('./helpers');
 
var infoController = {};
 
infoController.get = function (req, res, callback) {
	var userData;
	async.waterfall([
		function (next) {
			accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next);
		},
		function (_userData, next) {
			userData = _userData;
			if (!userData) {
				return callback();
			}
			async.parallel({
				history: async.apply(user.getModerationHistory, userData.uid),
				sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID),
				usernames: async.apply(user.getHistory, 'user:' + userData.uid + ':usernames'),
				emails: async.apply(user.getHistory, 'user:' + userData.uid + ':emails')
			}, next);
		}
	], function (err, data) {
		if (err) {
			return callback(err);
		}
 
		userData.history = data.history;
		userData.sessions = data.sessions;
		userData.usernames = data.usernames;
		userData.emails = data.emails;
		userData.title = '[[pages:account/info]]';
		userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:account_info]]'}]);
 
		res.render('account/info', userData);
	});
};
 
module.exports = infoController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/notifications.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/notifications.js

Statements: 12.5% (1 / 8)      Branches: 0% (0 / 2)      Functions: 0% (0 / 2)      Lines: 12.5% (1 / 8)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26    2                                              
'use strict';
 
var user = require('../../user'),
	helpers = require('../helpers');
 
 
var notificationsController = {};
 
notificationsController.get = function (req, res, next) {
	user.notifications.getAll(req.uid, 0, 39, function (err, notifications) {
		if (err) {
			return next(err);
		}
		res.render('notifications', {
			notifications: notifications,
			nextStart: 40,
			title: '[[pages:notifications]]',
			breadcrumbs: helpers.buildBreadcrumbs([{text: '[[pages:notifications]]'}])
		});
	});
};
 
 
module.exports = notificationsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/posts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/posts.js

Statements: 4.92% (3 / 61)      Branches: 0% (0 / 12)      Functions: 0% (0 / 15)      Lines: 4.92% (3 / 61)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161      2   2                                                                                                                                                                                           1                                                                                                                          
'use strict';
 
 
var async = require('async');
 
var db = require('../../database');
var user = require('../../user');
var posts = require('../../posts');
var topics = require('../../topics');
var pagination = require('../../pagination');
var helpers = require('../helpers');
var accountHelpers = require('./helpers');
 
var postsController = {};
 
postsController.getBookmarks = function (req, res, next) {
	var data = {
		template: 'account/bookmarks',
		set: 'bookmarks',
		type: 'posts',
		noItemsFoundKey: '[[topic:bookmarks.has_no_bookmarks]]',
		method: posts.getPostSummariesFromSet,
		crumb: '[[user:bookmarks]]'
	};
	getFromUserSet(data, req, res, next);
};
 
postsController.getPosts = function (req, res, next) {
	var data = {
		template: 'account/posts',
		set: 'posts',
		type: 'posts',
		noItemsFoundKey: '[[user:has_no_posts]]',
		method: posts.getPostSummariesFromSet,
		crumb: '[[global:posts]]'
	};
	getFromUserSet(data, req, res, next);
};
 
postsController.getUpVotedPosts = function (req, res, next) {
	var data = {
		template: 'account/upvoted',
		set: 'upvote',
		type: 'posts',
		noItemsFoundKey: '[[user:has_no_upvoted_posts]]',
		method: posts.getPostSummariesFromSet,
		crumb: '[[global:upvoted]]'
	};
	getFromUserSet(data, req, res, next);
};
 
postsController.getDownVotedPosts = function (req, res, next) {
	var data = {
		template: 'account/downvoted',
		set: 'downvote',
		type: 'posts',
		noItemsFoundKey: '[[user:has_no_downvoted_posts]]',
		method: posts.getPostSummariesFromSet,
		crumb: '[[global:downvoted]]'
	};
	getFromUserSet(data, req, res, next);
};
 
postsController.getBestPosts = function (req, res, next) {
	var data = {
		template: 'account/best',
		set: 'posts:votes',
		type: 'posts',
		noItemsFoundKey: '[[user:has_no_voted_posts]]',
		method: posts.getPostSummariesFromSet,
		crumb: '[[global:best]]'
	};
	getFromUserSet(data, req, res, next);
};
 
postsController.getWatchedTopics = function (req, res, next) {
	var data = {
		template: 'account/watched',
		set: 'followed_tids',
		type: 'topics',
		noItemsFoundKey: '[[user:has_no_watched_topics]]',
		method: topics.getTopicsFromSet,
		crumb: '[[user:watched]]'
	};
	getFromUserSet(data, req, res, next);
};
 
postsController.getTopics = function (req, res, next) {
	var data = {
		template: 'account/topics',
		set: 'topics',
		type: 'topics',
		noItemsFoundKey: '[[user:has_no_topics]]',
		method: topics.getTopicsFromSet,
		crumb: '[[global:topics]]'
	};
	getFromUserSet(data, req, res, next);
};
 
function getFromUserSet(data, req, res, callback) {
	var userData;
	var itemsPerPage;
	var page = Math.max(1, parseInt(req.query.page, 10) || 1);
	async.waterfall([
		function (next) {
			async.parallel({
				settings: function (next) {
					user.getSettings(req.uid, next);
				},
				userData: function (next) {
					accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next);
				}
			}, next);
		},
		function (results, next) {
			if (!results.userData) {
				return callback();
			}
 
			userData = results.userData;
 
			var setName = 'uid:' + userData.uid + ':' + data.set;
 
			itemsPerPage = (data.template === 'account/topics' || data.template === 'account/watched') ? results.settings.topicsPerPage : results.settings.postsPerPage;
 
			async.parallel({
				itemCount: function (next) {
					if (results.settings.usePagination) {
						db.sortedSetCard(setName, next);
					} else {
						next(null, 0);
					}
				},
				data: function (next) {
					var start = (page - 1) * itemsPerPage;
					var stop = start + itemsPerPage - 1;
					data.method(setName, req.uid, start, stop, next);
				}
			}, next);
		}
	], function (err, results) {
		if (err) {
			return callback(err);
		}
 
		userData[data.type] = results.data[data.type];
		userData.nextStart = results.data.nextStart;
 
		var pageCount = Math.ceil(results.itemCount / itemsPerPage);
		userData.pagination = pagination.create(page, pageCount);
 
		userData.noItemsFoundKey = data.noItemsFoundKey;
		userData.title = '[[pages:' + data.template + ', ' + userData.username + ']]';
		userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: data.crumb}]);
 
		res.render(data.template, userData);
	});
}
 
module.exports = postsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/profile.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/profile.js

Statements: 6.15% (4 / 65)      Branches: 0% (0 / 39)      Functions: 0% (0 / 11)      Lines: 6.15% (4 / 65)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141    4 4 4   4                                                                                                                                                                                                                                                                            
'use strict';
 
var nconf = require('nconf');
var async = require('async');
var S = require('string');
 
var user = require('../../user');
var posts = require('../../posts');
var plugins = require('../../plugins');
var meta = require('../../meta');
var accountHelpers = require('./helpers');
var helpers = require('../helpers');
var pagination = require('../../pagination');
var messaging = require('../../messaging');
 
var profileController = {};
 
profileController.get = function (req, res, callback) {
	var lowercaseSlug = req.params.userslug.toLowerCase();
 
	if (req.params.userslug !== lowercaseSlug) {
		if (res.locals.isAPI) {
			req.params.userslug = lowercaseSlug;
		} else {
			return res.redirect(nconf.get('relative_path') + '/user/' + lowercaseSlug);
		}
	}
	var page = Math.max(1, parseInt(req.query.page, 10) || 1);
	var itemsPerPage = 10;
	var start = (page - 1) * itemsPerPage;
	var stop = start + itemsPerPage - 1;
	var userData;
	async.waterfall([
		function (next) {
			accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next);
		},
		function (_userData, next) {
			if (!_userData) {
				return callback();
			}
			userData = _userData;
 
			req.session.uids_viewed = req.session.uids_viewed || {};
			if (req.uid !== parseInt(userData.uid, 10) && (!req.session.uids_viewed[userData.uid] || req.session.uids_viewed[userData.uid] < Date.now() - 3600000)) {
				user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
				req.session.uids_viewed[userData.uid] = Date.now();
			}
 
			async.parallel({
				hasPrivateChat: function (next) {
					messaging.hasPrivateChat(req.uid, userData.uid, next);
				},
				posts: function (next) {
					posts.getPostSummariesFromSet('uid:' + userData.theirid + ':posts', req.uid, start, stop, next);
				},
				signature: function (next) {
					posts.parseSignature(userData, req.uid, next);
				},
				aboutme: function (next) {
					if (userData.aboutme) {
						plugins.fireHook('filter:parse.aboutme', userData.aboutme, next);
					} else {
						next();
					}
				}
			}, next);
		},
		function (results, next) {
			if (parseInt(meta.config['reputation:disabled'], 10) === 1) {
				delete userData.reputation;
			}
 
			userData.posts = results.posts.posts.filter(function (p) {
				return p && parseInt(p.deleted, 10) !== 1;
			});
			userData.hasPrivateChat = results.hasPrivateChat;
			userData.aboutme = results.aboutme;
			userData.nextStart = results.posts.nextStart;
			userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username}]);
			userData.title = userData.username;
			var pageCount = Math.ceil(userData.postcount / itemsPerPage);
			userData.pagination = pagination.create(page, pageCount, req.query);
 
			userData['cover:url'] = userData['cover:url'] || require('../../coverPhoto').getDefaultProfileCover(userData.uid);
			userData['cover:position'] = userData['cover:position'] || '50% 50%';
 
			if (!parseInt(userData.profileviews, 10)) {
				userData.profileviews = 1;
			}
 
			var plainAboutMe = userData.aboutme ? S(userData.aboutme).decodeHTMLEntities().stripTags().s : '';
 
			res.locals.metaTags = [
				{
					name: "title",
					content: userData.fullname || userData.username
				},
				{
					name: "description",
					content: plainAboutMe
				},
				{
					property: 'og:title',
					content: userData.fullname || userData.username
				},
				{
					property: 'og:description',
					content: plainAboutMe
				}
			];
 
			if (userData.picture) {
				res.locals.metaTags.push(
					{
						property: 'og:image',
						content: userData.picture,
						noEscape: true
					},
					{
						property: "og:image:url",
						content: userData.picture,
						noEscape: true
					}
				);
			}
			userData.selectedGroup = userData.groups.find(function (group) {
				return group && group.name === userData.groupTitle;
			});
 
			plugins.fireHook('filter:user.account', {userData: userData, uid: req.uid}, next);
		}
	], function (err, results) {
		if (err) {
			return callback(err);
		}
		res.render('account/profile', results.userData);
	});
};
 
module.exports = profileController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/session.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/session.js

Statements: 6.67% (2 / 30)      Branches: 0% (0 / 15)      Functions: 0% (0 / 8)      Lines: 6.67% (2 / 30)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60    2   2                                                                                                              
'use strict';
 
var async = require('async');
 
var db = require('../../database');
var user = require('../../user');
 
var sessionController = {};
 
sessionController.revoke = function (req, res, next) {
	if (!req.params.hasOwnProperty('uuid')) {
		return next();
	}
 
	var _id;
	var uid;
	async.waterfall([
		function (next) {
			user.getUidByUserslug(req.params.userslug, next);
		},
		function (_uid, next) {
			uid = _uid;
			if (!uid) {
				return next(new Error('[[error:no-session-found]]'));
			}
			db.getSortedSetRange('uid:' + uid + ':sessions', 0, -1, next);
		},
		function (sids, done) {
			async.eachSeries(sids, function (sid, next) {
				db.sessionStore.get(sid, function (err, sessionObj) {
					if (err) {
						return next(err);
					}
					if (sessionObj && sessionObj.meta && sessionObj.meta.uuid === req.params.uuid) {
						_id = sid;
						done();
					} else {
						next();
					}
				});
			}, next);
		},
		function (next) {
			if (!_id) {
				return next(new Error('[[error:no-session-found]]'));
			}
 
			user.auth.revokeSession(_id, uid, next);
		}
	], function (err) {
		if (err) {
			return res.status(500).send(err.message);
		} else {
			return res.sendStatus(200);
		}
	});
};
 
module.exports = sessionController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/settings.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/accounts/settings.js

Statements: 4.41% (3 / 68)      Branches: 0% (0 / 12)      Functions: 0% (0 / 23)      Lines: 4.41% (3 / 68)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193    2   2                                                                                                                                                                                                                                                                                       1                                                                                                
'use strict';
 
var async = require('async');
 
var user = require('../../user');
var languages = require('../../languages');
var meta = require('../../meta');
var plugins = require('../../plugins');
var privileges = require('../../privileges');
var categories = require('../../categories');
var db = require('../../database');
var helpers = require('../helpers');
var accountHelpers = require('./helpers');
 
 
var settingsController = {};
 
 
settingsController.get = function (req, res, callback) {
	var userData;
	async.waterfall([
		function (next) {
			accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, next);
		},
		function (_userData, next) {
			userData = _userData;
			if (!userData) {
				return callback();
			}
			async.parallel({
				settings: function (next) {
					user.getSettings(userData.uid, next);
				},
				languages: function (next) {
					languages.list(next);
				},
				homePageRoutes: function (next) {
					getHomePageRoutes(next);
				},
				sounds: function (next) {
					meta.sounds.getFiles(next);
				},
				soundsMapping: function (next) {
					meta.sounds.getMapping(userData.uid, next);
				}
			}, next);
		},
		function (results, next) {
			userData.settings = results.settings;
			userData.languages = results.languages;
			userData.homePageRoutes = results.homePageRoutes;
 
			var soundSettings = {
				'notificationSound': 'notification',
				'incomingChatSound': 'chat-incoming',
				'outgoingChatSound': 'chat-outgoing'
			};
 
			Object.keys(soundSettings).forEach(function (setting) {
				userData[setting] = Object.keys(results.sounds).map(function (name) {
					return {name: name, selected: name === results.soundsMapping[soundSettings[setting]]};
				});
			});
 
			plugins.fireHook('filter:user.customSettings', {settings: results.settings, customSettings: [], uid: req.uid}, next);
		},
		function (data, next) {
			userData.customSettings = data.customSettings;
			userData.disableEmailSubscriptions = parseInt(meta.config.disableEmailSubscriptions, 10) === 1;
			next();
		}
	], function (err) {
		if (err) {
			return callback(err);
		}
 
		userData.dailyDigestFreqOptions = [
			{value: 'off', name: '[[user:digest_off]]', selected: 'off' === userData.settings.dailyDigestFreq},
			{value: 'day', name: '[[user:digest_daily]]', selected: 'day' === userData.settings.dailyDigestFreq},
			{value: 'week', name: '[[user:digest_weekly]]', selected: 'week' === userData.settings.dailyDigestFreq},
			{value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq}
		];
 
 
		userData.bootswatchSkinOptions = [
			{ "name": "Default", "value": "default" },
			{ "name": "Cerulean", "value": "cerulean" },
			{ "name": "Cosmo", "value": "cosmo"	},
			{ "name": "Cyborg", "value": "cyborg" },
			{ "name": "Darkly", "value": "darkly" },
			{ "name": "Flatly", "value": "flatly" },
			{ "name": "Journal", "value": "journal"	},
			{ "name": "Lumen", "value": "lumen" },
			{ "name": "Paper", "value": "paper" },
			{ "name": "Readable", "value": "readable" },
			{ "name": "Sandstone", "value": "sandstone" },
			{ "name": "Simplex", "value": "simplex" },
			{ "name": "Slate", "value": "slate"	},
			{ "name": "Spacelab", "value": "spacelab" },
			{ "name": "Superhero", "value": "superhero" },
			{ "name": "United", "value": "united" },
			{ "name": "Yeti", "value": "yeti" }
		];
 
		var isCustom = true;
		userData.homePageRoutes.forEach(function (route) {
			route.selected = route.route === userData.settings.homePageRoute;
			if (route.selected) {
				isCustom = false;
			}
		});
 
		if (isCustom && userData.settings.homePageRoute === 'none') {
			isCustom = false;
		}
 
		userData.homePageRoutes.push({
		 	route: 'custom',
		 	name: 'Custom',
		 	selected: isCustom
		});
 
		userData.bootswatchSkinOptions.forEach(function (skin) {
			skin.selected = skin.value === userData.settings.bootswatchSkin;
		});
 
		userData.languages.forEach(function (language) {
			language.selected = language.code === userData.settings.userLang;
		});
 
		userData.disableCustomUserSkins = parseInt(meta.config.disableCustomUserSkins, 10) === 1;
 
		userData.allowUserHomePage = parseInt(meta.config.allowUserHomePage, 10) === 1;
 
		userData.inTopicSearchAvailable = plugins.hasListeners('filter:topic.search');
 
		userData.title = '[[pages:account/settings]]';
		userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:settings]]'}]);
 
		res.render('account/settings', userData);
	});
};
 
 
function getHomePageRoutes(callback) {
	async.waterfall([
		function (next) {
			db.getSortedSetRange('cid:0:children', 0, -1, next);
		},
		function (cids, next) {
			privileges.categories.filterCids('find', cids, 0, next);
		},
		function (cids, next) {
			categories.getCategoriesFields(cids, ['name', 'slug'], next);
		},
		function (categoryData, next) {
			categoryData = categoryData.map(function (category) {
				return {
					route: 'category/' + category.slug,
					name: 'Category: ' + category.name
				};
			});
 
			categoryData = categoryData || [];
 
			plugins.fireHook('filter:homepage.get', {routes: [
				{
					route: 'categories',
					name: 'Categories'
				},
				{
					route: 'unread',
					name: 'Unread'
				},
				{
					route: 'recent',
					name: 'Recent'
				},
				{
					route: 'popular',
					name: 'Popular'
				}
			].concat(categoryData)}, next);
		},
		function (data, next) {
			next(null, data.routes);
		}
	], callback);
}
 
 
module.exports = settingsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/

Statements: 14.12% (83 / 588)      Branches: 0% (0 / 189)      Functions: 0% (0 / 169)      Lines: 14.12% (83 / 588)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/
File Statements Branches Functions Lines
appearance.js 60% (3 / 5) 0% (0 / 2) 0% (0 / 1) 60% (3 / 5)
blacklist.js 12.5% (1 / 8) 0% (0 / 2) 0% (0 / 2) 12.5% (1 / 8)
cache.js 27.27% (3 / 11) 0% (0 / 4) 0% (0 / 1) 27.27% (3 / 11)
categories.js 7.41% (2 / 27) 0% (0 / 8) 0% (0 / 6) 7.41% (2 / 27)
dashboard.js 15.38% (6 / 39) 0% (0 / 6) 0% (0 / 17) 15.38% (6 / 39)
database.js 29.41% (5 / 17) 0% (0 / 6) 0% (0 / 4) 29.41% (5 / 17)
errors.js 18.75% (3 / 16) 0% (0 / 4) 0% (0 / 4) 18.75% (3 / 16)
events.js 11.11% (2 / 18) 0% (0 / 4) 0% (0 / 4) 11.11% (2 / 18)
flags.js 8.7% (4 / 46) 0% (0 / 27) 0% (0 / 11) 8.7% (4 / 46)
groups.js 5.56% (2 / 36) 0% (0 / 10) 0% (0 / 10) 5.56% (2 / 36)
homepage.js 9.09% (2 / 22) 0% (0 / 6) 0% (0 / 8) 9.09% (2 / 22)
info.js 21.28% (10 / 47) 0% (0 / 12) 0% (0 / 17) 21.28% (10 / 47)
languages.js 9.09% (1 / 11) 0% (0 / 4) 0% (0 / 3) 9.09% (1 / 11)
logger.js 75% (3 / 4) 100% (0 / 0) 0% (0 / 1) 75% (3 / 4)
navigation.js 27.27% (3 / 11) 0% (0 / 2) 0% (0 / 3) 27.27% (3 / 11)
rewards.js 42.86% (3 / 7) 0% (0 / 2) 0% (0 / 2) 42.86% (3 / 7)
settings.js 11.11% (3 / 27) 0% (0 / 12) 0% (0 / 8) 11.11% (3 / 27)
social.js 12.5% (1 / 8) 0% (0 / 2) 0% (0 / 2) 12.5% (1 / 8)
sounds.js 10% (1 / 10) 0% (0 / 2) 0% (0 / 3) 10% (1 / 10)
tags.js 12.5% (1 / 8) 0% (0 / 2) 0% (0 / 2) 12.5% (1 / 8)
themes.js 38.46% (5 / 13) 0% (0 / 6) 0% (0 / 2) 38.46% (5 / 13)
uploads.js 12.09% (11 / 91) 0% (0 / 38) 0% (0 / 22) 12.09% (11 / 91)
users.js 5.05% (5 / 99) 0% (0 / 26) 0% (0 / 34) 5.05% (5 / 99)
widgets.js 42.86% (3 / 7) 0% (0 / 2) 0% (0 / 2) 42.86% (3 / 7)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/appearance.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/appearance.js

Statements: 60% (3 / 5)      Branches: 0% (0 / 2)      Functions: 0% (0 / 1)      Lines: 60% (3 / 5)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14    1   1             1    
"use strict";
 
var appearanceController = {};
 
appearanceController.get = function (req, res, next) {
	var term = req.params.term ? req.params.term : 'themes';
 
	res.render('admin/appearance/' + term, {});
};
 
 
module.exports = appearanceController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/blacklist.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/blacklist.js

Statements: 12.5% (1 / 8)      Branches: 0% (0 / 2)      Functions: 0% (0 / 2)      Lines: 12.5% (1 / 8)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18    2                              
"use strict";
 
var meta = require('../../meta');
 
var blacklistController = {};
 
blacklistController.get = function (req, res, next) {
	meta.blacklist.get(function (err, rules) {
		if (err) {
			return next(err);
		}
		res.render('admin/manage/ip-blacklist', {rules: rules, title: 'IP Blacklist'});
	});
};
 
module.exports = blacklistController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/cache.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/cache.js

Statements: 27.27% (3 / 11)      Branches: 0% (0 / 4)      Functions: 0% (0 / 1)      Lines: 27.27% (3 / 11)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36    1   1                                                           1  
'use strict';
 
var cacheController = {};
 
cacheController.get = function (req, res, next) {
	var postCache = require('../../posts/cache');
	var groupCache = require('../../groups').cache;
 
	var avgPostSize = 0;
	var percentFull = 0;
	if (postCache.itemCount > 0) {
		avgPostSize = parseInt((postCache.length / postCache.itemCount), 10);
		percentFull = ((postCache.length / postCache.max) * 100).toFixed(2);
	}
 
	res.render('admin/advanced/cache', {
		postCache: {
			length: postCache.length,
			max: postCache.max,
			itemCount: postCache.itemCount,
			percentFull: percentFull,
			avgPostSize: avgPostSize
		},
		groupCache: {
			length: groupCache.length,
			max: groupCache.max,
			itemCount: groupCache.itemCount,
			percentFull: ((groupCache.length / groupCache.max) * 100).toFixed(2),
			dump: req.query.debug ? JSON.stringify(groupCache.dump(), null, 4) : false
		}
	});
};
 
 
module.exports = cacheController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/categories.js

Statements: 7.41% (2 / 27)      Branches: 0% (0 / 8)      Functions: 0% (0 / 6)      Lines: 7.41% (2 / 27)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62    2   2                                                                                                                  
"use strict";
 
var async = require('async');
 
var categories = require('../../categories');
var privileges = require('../../privileges');
var analytics = require('../../analytics');
var plugins = require('../../plugins');
var translator = require('../../../public/src/modules/translator');
 
 
var categoriesController = {};
 
categoriesController.get = function (req, res, next) {
	async.parallel({
		category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
		privileges: async.apply(privileges.categories.list, req.params.category_id)
	}, function (err, data) {
		if (err) {
			return next(err);
		}
		var category = data.category[0];
 
		if (!category) {
			return next();
		}
 
		plugins.fireHook('filter:admin.category.get', { req: req, res: res, category: category, privileges: data.privileges }, function (err, data) {
			if (err) {
				return next(err);
			}
			data.category.name = translator.escape(String(data.category.name));
			res.render('admin/manage/category', {
				category: data.category,
				privileges: data.privileges
			});
		});
	});
};
 
categoriesController.getAll = function (req, res, next) {
	// Categories list will be rendered on client side with recursion, etc.
	res.render('admin/manage/categories', {});
};
 
categoriesController.getAnalytics = function (req, res, next) {
	async.parallel({
		name: async.apply(categories.getCategoryField, req.params.category_id, 'name'),
		analytics: async.apply(analytics.getCategoryAnalytics, req.params.category_id)
	}, function (err, data) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/manage/category-analytics', data);
	});
};
 
 
module.exports = categoriesController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/dashboard.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/dashboard.js

Statements: 15.38% (6 / 39)      Branches: 0% (0 / 6)      Functions: 0% (0 / 17)      Lines: 15.38% (6 / 39)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105    4 4   4                                                                                 1                                                     1                                               1              
'use strict';
 
var async = require('async');
var nconf = require('nconf');
 
var db = require('../../database');
var meta = require('../../meta');
var plugins = require('../../plugins');
 
var dashboardController = {};
 
 
dashboardController.get = function (req, res, next) {
	async.parallel({
		stats: function (next) {
			getStats(next);
		},
		notices: function (next) {
			var notices = [
				{
					done: !meta.reloadRequired,
					doneText: 'Restart not required',
					notDoneText:'Restart required'
				},
				{
					done: plugins.hasListeners('filter:search.query'),
					doneText: 'Search Plugin Installed',
					notDoneText:'Search Plugin not installed',
					tooltip: 'Install a search plugin from the plugin page in order to activate search functionality',
					link:'/admin/extend/plugins'
				}
			];
			plugins.fireHook('filter:admin.notices', notices, next);
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
		res.render('admin/general/dashboard', {
			version: nconf.get('version'),
			notices: results.notices,
			stats: results.stats
		});
	});
};
 
function getStats(callback) {
	async.parallel([
		function (next) {
			getStatsForSet('ip:recent', 'uniqueIPCount', next);
		},
		function (next) {
			getStatsForSet('users:joindate', 'userCount', next);
		},
		function (next) {
			getStatsForSet('posts:pid', 'postCount', next);
		},
		function (next) {
			getStatsForSet('topics:tid', 'topicCount', next);
		}
	], function (err, results) {
		if (err) {
			return callback(err);
		}
		results[0].name = 'Unique Visitors';
		results[1].name = 'Users';
		results[2].name = 'Posts';
		results[3].name = 'Topics';
 
		callback(null, results);
	});
}
 
function getStatsForSet(set, field, callback) {
	var terms = {
		day: 86400000,
		week: 604800000,
		month: 2592000000
	};
 
	var now = Date.now();
	async.parallel({
		day: function (next) {
			db.sortedSetCount(set, now - terms.day, '+inf', next);
		},
		week: function (next) {
			db.sortedSetCount(set, now - terms.week, '+inf', next);
		},
		month: function (next) {
			db.sortedSetCount(set, now - terms.month, '+inf', next);
		},
		alltime: function (next) {
			getGlobalField(field, next);
		}
	}, callback);
}
 
function getGlobalField(field, callback) {
	db.getObjectField('global', field, function (err, count) {
		callback(err, parseInt(count, 10) || 0);
	});
}
 
module.exports = dashboardController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/database.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/database.js

Statements: 29.41% (5 / 17)      Branches: 0% (0 / 6)      Functions: 0% (0 / 4)      Lines: 29.41% (5 / 17)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37    1 1   1       1                                                   1  
'use strict';
 
var async = require('async');
var nconf = require('nconf');
 
var databaseController = {};
 
 
 
databaseController.get = function (req, res, next) {
	async.parallel({
		redis: function (next) {
			if (nconf.get('redis')) {
				var rdb = require('../../database/redis');
				rdb.info(rdb.client, next);
			} else {
				next();
			}
		},
		mongo: function (next) {
			if (nconf.get('mongo')) {
				var mdb = require('../../database/mongo');
				mdb.info(mdb.client, next);
			} else {
				next();
			}
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
		res.render('admin/advanced/database', results);
	});
};
 
module.exports = databaseController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/errors.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/errors.js

Statements: 18.75% (3 / 16)      Branches: 0% (0 / 4)      Functions: 0% (0 / 4)      Lines: 18.75% (3 / 16)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39    2 2   2                                                                  
'use strict';
 
var async = require('async');
var json2csv = require('json-2-csv').json2csv;
 
var meta = require('../../meta');
var analytics = require('../../analytics');
 
var errorsController = {};
 
errorsController.get = function (req, res, next) {
	async.parallel({
		'not-found': async.apply(meta.errors.get, true),
		analytics: async.apply(analytics.getErrorAnalytics)
	}, function (err, data) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/advanced/errors', data);
	});
};
 
errorsController.export = function (req, res, next) {
	async.waterfall([
		async.apply(meta.errors.get, false),
		async.apply(json2csv)
	], function (err, csv) {
		if (err) {
			return next(err);
		}
 
		res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="404.csv"').send(csv);
	});
};
 
 
module.exports = errorsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/events.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/events.js

Statements: 11.11% (2 / 18)      Branches: 0% (0 / 4)      Functions: 0% (0 / 4)      Lines: 11.11% (2 / 18)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43    2   2                                                                            
'use strict';
 
var async = require('async');
 
var db = require('../../database');
var events = require('../../events');
var pagination = require('../../pagination');
 
var eventsController = {};
 
 
eventsController.get = function (req, res, next) {
 
	var page = parseInt(req.query.page, 10) || 1;
	var itemsPerPage = 20;
	var start = (page - 1) * itemsPerPage;
	var stop = start + itemsPerPage - 1;
 
	async.parallel({
		eventCount: function (next) {
			db.sortedSetCard('events:time', next);
		},
		events: function (next) {
			events.getEvents(start, stop, next);
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
 
		var pageCount = Math.max(1, Math.ceil(results.eventCount / itemsPerPage));
 
		res.render('admin/advanced/events', {
			events: results.events,
			pagination: pagination.create(page, pageCount),
			next: 20
		});
	});
};
 
 
module.exports = eventsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/flags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/flags.js

Statements: 8.7% (4 / 46)      Branches: 0% (0 / 27)      Functions: 0% (0 / 11)      Lines: 8.7% (4 / 46)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105    2 2   2                                                                                                                                       1                                                              
"use strict";
 
var async = require('async');
var validator = require('validator');
 
var posts = require('../../posts');
var user = require('../../user');
var categories = require('../../categories');
var analytics = require('../../analytics');
var pagination = require('../../pagination');
 
var flagsController = {};
 
var itemsPerPage = 20;
 
flagsController.get = function (req, res, next) {
	var byUsername = req.query.byUsername || '';
	var cid = req.query.cid || 0;
	var sortBy = req.query.sortBy || 'count';
	var page = parseInt(req.query.page, 10) || 1;
 
	async.parallel({
		categories: function (next) {
			categories.buildForSelect(req.uid, next);
		},
		flagData: function (next) {
			getFlagData(req, res, next);
		},
		analytics: function (next) {
			analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30, next);
		},
		assignees: async.apply(user.getAdminsandGlobalModsandModerators)
	}, function (err, results) {
		if (err) {
			return next(err);
		}
 
		// Minimise data set for assignees so tjs does less work
		results.assignees = results.assignees.map(function (userObj) {
			return {
				uid: userObj.uid,
				username: userObj.username
			};
		});
 
		// If res.locals.cids is populated, then slim down the categories list
		if (res.locals.cids) {
			results.categories = results.categories.filter(function (category) {
				return res.locals.cids.indexOf(String(category.cid)) !== -1;
			});
		}
 
		var pageCount = Math.max(1, Math.ceil(results.flagData.count / itemsPerPage));
 
		results.categories.forEach(function (category) {
			category.selected = parseInt(category.cid, 10) === parseInt(cid, 10);
		});
 
		var data = {
			posts: results.flagData.posts,
			assignees: results.assignees,
			analytics: results.analytics,
			categories: results.categories,
			byUsername: validator.escape(String(byUsername)),
			sortByCount: sortBy === 'count',
			sortByTime: sortBy === 'time',
			pagination: pagination.create(page, pageCount, req.query),
			title: '[[pages:flagged-posts]]'
		};
		res.render('admin/manage/flags', data);
	});
};
 
function getFlagData(req, res, callback) {
	var sortBy = req.query.sortBy || 'count';
	var byUsername = req.query.byUsername || '';
	var cid = req.query.cid || res.locals.cids || 0;
	var page = parseInt(req.query.page, 10) || 1;
	var start = (page - 1) * itemsPerPage;
	var stop = start + itemsPerPage - 1;
 
	var sets = [sortBy === 'count' ? 'posts:flags:count' : 'posts:flagged'];
 
	async.waterfall([
		function (next) {
			if (byUsername) {
				user.getUidByUsername(byUsername, next);
			} else {
				process.nextTick(next, null, 0);
			}
		},
		function (uid, next) {
			if (uid) {
				sets.push('uid:' + uid + ':flag:pids');
			}
 
			posts.getFlags(sets, cid, req.uid, start, stop, next);
		}
	], callback);
}
 
 
module.exports = flagsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/groups.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/groups.js

Statements: 5.56% (2 / 36)      Branches: 0% (0 / 10)      Functions: 0% (0 / 10)      Lines: 5.56% (2 / 36)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75    2   2                                                                                                                                            
"use strict";
 
var async = require('async');
 
var db = require('../../database');
var groups = require('../../groups');
var meta = require('../../meta');
var pagination = require('../../pagination');
var helpers = require('../helpers');
 
 
var groupsController = {};
 
 
groupsController.list = function (req, res, next) {
	var page = parseInt(req.query.page, 10) || 1;
	var groupsPerPage = 20;
	var pageCount = 0;
 
	async.waterfall([
		function (next) {
			db.getSortedSetRevRange('groups:createtime', 0, -1, next);
		},
		function (groupNames, next) {
			groupNames = groupNames.filter(function (name) {
				return name.indexOf(':privileges:') === -1 && name !== 'registered-users';
			});
			pageCount = Math.ceil(groupNames.length / groupsPerPage);
 
			var start = (page - 1) * groupsPerPage;
			var stop =  start + groupsPerPage - 1;
 
			groupNames = groupNames.slice(start, stop + 1);
			groups.getGroupsData(groupNames, next);
		},
		function (groupData, next) {
			next(null, {groups: groupData, pagination: pagination.create(page, pageCount)});
		}
	], function (err, data) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/manage/groups', {
			groups: data.groups,
			pagination: data.pagination,
			yourid: req.uid
		});
	});
};
 
groupsController.get = function (req, res, callback) {
	var groupName = req.params.name;
	async.waterfall([
		function (next) {
			groups.exists(groupName, next);
		},
		function (exists, next) {
			if (!exists) {
				return callback();
			}
			groups.get(groupName, {uid: req.uid, truncateUserList: true, userListCount: 20}, next);
		}
	], function (err, group) {
		if (err) {
			return callback(err);
		}
		group.isOwner = true;
		res.render('admin/manage/group', {group: group, allowPrivateGroups: parseInt(meta.config.allowPrivateGroups, 10) === 1});
	});
};
 
module.exports = groupsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/homepage.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/homepage.js

Statements: 9.09% (2 / 22)      Branches: 0% (0 / 6)      Functions: 0% (0 / 8)      Lines: 9.09% (2 / 22)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67    2   2                                                                                                                            
'use strict';
 
var async = require('async');
 
var db = require('../../database');
var categories = require('../../categories');
var privileges = require('../../privileges');
var plugins = require('../../plugins');
 
var homePageController = {};
 
 
homePageController.get = function (req, res, next) {
	async.waterfall([
		function (next) {
			db.getSortedSetRange('categories:cid', 0, -1, next);
		},
		function (cids, next) {
			privileges.categories.filterCids('find', cids, 0, next);
		},
		function (cids, next) {
			categories.getCategoriesFields(cids, ['name', 'slug'], next);
		},
		function (categoryData, next) {
			categoryData = categoryData.map(function (category) {
				return {
					route: 'category/' + category.slug,
					name: 'Category: ' + category.name
				};
			});
			next(null, categoryData);
		}
	], function (err, categoryData) {
		if (err || !categoryData) {
			categoryData = [];
		}
 
		plugins.fireHook('filter:homepage.get', {routes: [
			{
				route: 'categories',
				name: 'Categories'
			},
			{
				route: 'recent',
				name: 'Recent'
			},
			{
				route: 'popular',
				name: 'Popular'
			}
		].concat(categoryData)}, function (err, data) {
			if (err) {
				return next(err);
			}
 
			data.routes.push({
				route: '',
				name: 'Custom'
			});
 
			res.render('admin/general/homepage', data);
		});
	});
};
 
module.exports = homePageController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/info.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/info.js

Statements: 21.28% (10 / 47)      Branches: 0% (0 / 12)      Functions: 0% (0 / 17)      Lines: 21.28% (10 / 47)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101    2 2 2 2 2   2 2                                                                   1                                                                         1 1                                      
'use strict';
 
var async = require('async');
var os = require('os');
var winston = require('winston');
var nconf = require('nconf');
var exec = require('child_process').exec;
 
var pubsub = require('../../pubsub');
var rooms = require('../../socket.io/admin/rooms');
 
var infoController = {};
 
var info = {};
 
infoController.get = function (req, res, next) {
	info = {};
	pubsub.publish('sync:node:info:start');
	setTimeout(function () {
		var data = [];
		Object.keys(info).forEach(function (key) {
			data.push(info[key]);
		});
		data.sort(function (a, b) {
			return (a.os.hostname < b.os.hostname) ? -1 : (a.os.hostname > b.os.hostname) ? 1 : 0;
		});
		res.render('admin/development/info', {info: data, infoJSON: JSON.stringify(data, null, 4), host: os.hostname(), port: nconf.get('port')});
	}, 500);
};
 
pubsub.on('sync:node:info:start', function () {
	getNodeInfo(function (err, data) {
		if (err) {
			return winston.error(err);
		}
		pubsub.publish('sync:node:info:end', {data: data, id: os.hostname() + ':' + nconf.get('port')});
	});
});
 
pubsub.on('sync:node:info:end', function (data) {
	info[data.id] = data.data;
});
 
function getNodeInfo(callback) {
	var data = {
		process: {
			port: nconf.get('port'),
			pid: process.pid,
			title: process.title,
			version: process.version,
			memoryUsage: process.memoryUsage(),
			uptime: process.uptime()
		},
		os: {
			hostname: os.hostname(),
			type: os.type(),
			platform: os.platform(),
			arch: os.arch(),
			release: os.release(),
			load: os.loadavg().map(function (load) { return load.toFixed(2); }).join(', ')
		}
	};
 
	async.parallel({
		stats: function (next) {
			rooms.getLocalStats(next);
		},
		gitInfo: function (next) {
			getGitInfo(next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
		data.git = results.gitInfo;
		data.stats = results.stats;
		callback(null, data);
	});
}
 
function getGitInfo(callback) {
	function get(cmd,  callback) {
		exec(cmd, function (err, stdout) {
			if (err) {
				winston.error(err);
			}
			callback(null, stdout ? stdout.replace(/\n$/, '') : 'no-git-info');
		});
	}
	async.parallel({
		hash: function (next) {
			get('git rev-parse HEAD', next);
		},
		branch: function (next) {
			get('git rev-parse --abbrev-ref HEAD', next);
		}
	}, callback);
}
 
module.exports = infoController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/languages.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/languages.js

Statements: 9.09% (1 / 11)      Branches: 0% (0 / 4)      Functions: 0% (0 / 3)      Lines: 9.09% (1 / 11)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26    2                                              
'use strict';
 
var languages = require('../../languages');
var meta = require('../../meta');
 
var languagesController = {};
 
 
languagesController.get = function (req, res, next) {
	languages.list(function (err, languages) {
		if (err) {
			return next(err);
		}
 
		languages.forEach(function (language) {
			language.selected = language.code === (meta.config.defaultLang || 'en-GB');
		});
 
		res.render('admin/general/languages', {
			languages: languages
		});
	});
};
 
module.exports = languagesController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/logger.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/logger.js

Statements: 75% (3 / 4)      Branches: 100% (0 / 0)      Functions: 0% (0 / 1)      Lines: 75% (3 / 4)      Ignored: none     

1 2 3 4 5 6 7 8 9 10    1   1       1  
'use strict';
 
var loggerController = {};
 
loggerController.get = function (req, res) {
	res.render('admin/development/logger', {});
};
 
module.exports = loggerController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/navigation.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/navigation.js

Statements: 27.27% (3 / 11)      Branches: 0% (0 / 2)      Functions: 0% (0 / 3)      Lines: 27.27% (3 / 11)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24    1   1                                   1  
'use strict';
 
var navigationController = {};
 
navigationController.get = function (req, res, next) {
	require('../../navigation/admin').getAdmin(function (err, data) {
		if (err) {
			return next(err);
		}
 
 
		data.enabled.forEach(function (enabled, index) {
			enabled.index = index;
			enabled.selected = index === 0;
		});
 
		data.navigation = data.enabled.slice();
 
		res.render('admin/general/navigation', data);
	});
};
 
module.exports = navigationController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/rewards.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/rewards.js

Statements: 42.86% (3 / 7)      Branches: 0% (0 / 2)      Functions: 0% (0 / 2)      Lines: 42.86% (3 / 7)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18    1   1                       1  
'use strict';
 
var rewardsController = {};
 
rewardsController.get = function (req, res, next) {
	require('../../rewards/admin').get(function (err, data) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/extend/rewards', data);
	});
};
 
 
 
module.exports = rewardsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/settings.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/settings.js

Statements: 11.11% (3 / 27)      Branches: 0% (0 / 12)      Functions: 0% (0 / 8)      Lines: 11.11% (3 / 27)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68      2 2                                   1                                                                                          
'use strict';
 
 
var async = require('async');
var meta = require('../../meta');
 
var settingsController = module.exports;
 
settingsController.get = function (req, res, next) {
	var term = req.params.term ? req.params.term : 'general';
 
	switch (req.params.term) {
		case 'email':
			renderEmail(req, res, next);
			break;
 
		default:
			res.render('admin/settings/' + term);
	}
};
 
 
function renderEmail(req, res, next) {
	var fs = require('fs');
	var path = require('path');
	var utils = require('../../../public/src/utils');
 
	var emailsPath = path.join(__dirname, '../../../public/templates/emails');
 
	async.waterfall([
		function (next) {
			utils.walk(emailsPath, next);
		},
		function (emails, next) {
			async.map(emails, function (email, next) {
				var path = email.replace(emailsPath, '').substr(1).replace('.tpl', '');
 
				fs.readFile(email, function (err, original) {
					if (err) {
						return next(err);
					}
 
					var text = meta.config['email:custom:' + path] ? meta.config['email:custom:' + path] : original.toString();
 
					next(null, {
						path: path,
						fullpath: email,
						text: text,
						original: original.toString()
					});
				});
			}, next);
		}
	], function (err, emails) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/settings/email', {
			emails: emails,
			sendable: emails.filter(function (email) {
				return email.path.indexOf('_plaintext') === -1 && email.path.indexOf('partials') === -1;
			})
		});
	});
}
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/social.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/social.js

Statements: 12.5% (1 / 8)      Branches: 0% (0 / 2)      Functions: 0% (0 / 2)      Lines: 12.5% (1 / 8)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21    2                                    
'use strict';
 
var social = require('../../social');
 
var socialController = {};
 
 
socialController.get = function (req, res, next) {
	social.getPostSharing(function (err, posts) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/general/social', {
			posts: posts
		});
	});
};
 
module.exports = socialController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/sounds.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/sounds.js

Statements: 10% (1 / 10)      Branches: 0% (0 / 2)      Functions: 0% (0 / 3)      Lines: 10% (1 / 10)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26    2                                              
'use strict';
 
var meta = require('../../meta');
 
var soundsController = {};
 
soundsController.get = function (req, res, next) {
	meta.sounds.getFiles(function (err, sounds) {
		if (err) {
			return next(err);
		}
 
		sounds = Object.keys(sounds).map(function (name) {
			return {
				name: name
			};
		});
 
		res.render('admin/general/sounds', {
			sounds: sounds
		});
	});
};
 
module.exports = soundsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/tags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/tags.js

Statements: 12.5% (1 / 8)      Branches: 0% (0 / 2)      Functions: 0% (0 / 2)      Lines: 12.5% (1 / 8)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20    2                                  
"use strict";
 
var topics = require('../../topics');
 
var tagsController = {};
 
tagsController.get = function (req, res, next) {
	topics.getTags(0, 199, function (err, tags) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/manage/tags', {tags: tags});
	});
};
 
 
module.exports = tagsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/themes.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/themes.js

Statements: 38.46% (5 / 13)      Branches: 0% (0 / 6)      Functions: 0% (0 / 2)      Lines: 38.46% (5 / 13)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26    1 1   1   1                                 1  
'use strict';
 
var path = require('path');
var file = require('../../file');
 
var themesController = {};
 
themesController.get = function (req, res, next) {
	var themeDir = path.join(__dirname, '../../../node_modules/' + req.params.theme);
	file.exists(themeDir, function (exists) {
		if (!exists) {
			return next();
		}
 
		var themeConfig = require(path.join(themeDir, 'theme.json')),
			screenshotPath = path.join(themeDir, themeConfig.screenshot);
		if (themeConfig.screenshot && file.existsSync(screenshotPath)) {
			res.sendFile(screenshotPath);
		} else {
			res.sendFile(path.join(__dirname, '../../../public/images/themes/default.png'));
		}
	});
};
 
module.exports = themesController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/uploads.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/uploads.js

Statements: 12.09% (11 / 91)      Branches: 0% (0 / 38)      Functions: 0% (0 / 22)      Lines: 12.09% (11 / 91)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182    2 2 2 2 2 2 2                                                                                                                                                                                                                                                           1                 1                             1 1                                            
"use strict";
 
var fs = require('fs');
var path = require('path');
var async = require('async');
var nconf = require('nconf');
var winston = require('winston');
var file = require('../../file');
var image = require('../../image');
var plugins = require('../../plugins');
 
var allowedImageTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif', 'image/svg+xml'];
 
var uploadsController = {};
 
uploadsController.uploadCategoryPicture = function (req, res, next) {
	var uploadedFile = req.files.files[0];
	var params = null;
 
	try {
		params = JSON.parse(req.body.params);
	} catch (e) {
		fs.unlink(uploadedFile.path, function (err) {
			if (err) {
				winston.error(err);
			}
		});
		return next(e);
	}
 
	if (validateUpload(req, res, next, uploadedFile, allowedImageTypes)) {
		var filename =  'category-' + params.cid + path.extname(uploadedFile.name);
		uploadImage(filename, 'category', uploadedFile, req, res, next);
	}
};
 
uploadsController.uploadFavicon = function (req, res, next) {
	var uploadedFile = req.files.files[0];
	var allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'];
 
	if (validateUpload(req, res, next, uploadedFile, allowedTypes)) {
		file.saveFileToLocal('favicon.ico', 'system', uploadedFile.path, function (err, image) {
			fs.unlink(uploadedFile.path, function (err) {
				if (err) {
					winston.error(err);
				}
			});
			if (err) {
				return next(err);
			}
 
			res.json([{name: uploadedFile.name, url: image.url}]);
		});
	}
};
 
uploadsController.uploadTouchIcon = function (req, res, next) {
	var uploadedFile = req.files.files[0],
		allowedTypes = ['image/png'],
		sizes = [36, 48, 72, 96, 144, 192];
 
	if (validateUpload(req, res, next, uploadedFile, allowedTypes)) {
		file.saveFileToLocal('touchicon-orig.png', 'system', uploadedFile.path, function (err, imageObj) {
			if (err) {
				return next(err);
			}
 
			// Resize the image into squares for use as touch icons at various DPIs
			async.each(sizes, function (size, next) {
				async.series([
					async.apply(file.saveFileToLocal, 'touchicon-' + size + '.png', 'system', uploadedFile.path),
					async.apply(image.resizeImage, {
						path: path.join(nconf.get('base_dir'), nconf.get('upload_path'), 'system', 'touchicon-' + size + '.png'),
						extension: 'png',
						width: size,
						height: size
					})
				], next);
			}, function (err) {
				fs.unlink(uploadedFile.path, function (err) {
					if (err) {
						winston.error(err);
					}
				});
 
				if (err) {
					return next(err);
				}
 
				res.json([{name: uploadedFile.name, url: imageObj.url}]);
			});
		});
	}
};
 
uploadsController.uploadLogo = function (req, res, next) {
	upload('site-logo', req, res, next);
};
 
uploadsController.uploadSound = function (req, res, next) {
	var uploadedFile = req.files.files[0];
 
	file.saveFileToLocal(uploadedFile.name, 'sounds', uploadedFile.path, function (err) {
		if (err) {
			return next(err);
		}
 
		var	soundsPath = path.join(__dirname, '../../../public/sounds'),
			filePath = path.join(__dirname, '../../../public/uploads/sounds', uploadedFile.name);
 
		if (process.platform === 'win32') {
			fs.link(filePath, path.join(soundsPath, path.basename(filePath)));
		} else {
			fs.symlink(filePath, path.join(soundsPath, path.basename(filePath)), 'file');
		}
 
		fs.unlink(uploadedFile.path, function (err) {
			if (err) {
				return next(err);
			}
 
			res.json([{}]);
		});
	});
};
 
uploadsController.uploadDefaultAvatar = function (req, res, next) {
	upload('avatar-default', req, res, next);
};
 
uploadsController.uploadOgImage = function (req, res, next) {
	upload('og:image', req, res, next);
};
 
function upload(name, req, res, next) {
	var uploadedFile = req.files.files[0];
 
	if (validateUpload(req, res, next, uploadedFile, allowedImageTypes)) {
		var filename = name + path.extname(uploadedFile.name);
		uploadImage(filename, 'system', uploadedFile, req, res, next);
	}
}
 
function validateUpload(req, res, next, uploadedFile, allowedTypes) {
	if (allowedTypes.indexOf(uploadedFile.type) === -1) {
		fs.unlink(uploadedFile.path, function (err) {
			if (err) {
				winston.error(err);
			}
		});
 
		res.json({error: '[[error:invalid-image-type, ' + allowedTypes.join('&#44; ') + ']]'});
		return false;
	}
 
	return true;
}
 
function uploadImage(filename, folder, uploadedFile, req, res, next) {
	function done(err, image) {
		fs.unlink(uploadedFile.path, function (err) {
			if (err) {
				winston.error(err);
			}
		});
		if (err) {
			return next(err);
		}
 
		res.json([{name: uploadedFile.name, url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
	}
 
	if (plugins.hasListeners('filter:uploadImage')) {
		plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.user.uid}, done);
	} else {
		file.saveFileToLocal(filename, folder, uploadedFile.path, done);
	}
}
 
module.exports = uploadsController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/users.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/users.js

Statements: 5.05% (5 / 99)      Branches: 0% (0 / 26)      Functions: 0% (0 / 34)      Lines: 5.05% (5 / 99)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199    2 2   2                                                                                                                                                                                                                                 1                                                                                               1                                                                
"use strict";
 
var async = require('async');
var validator = require('validator');
 
var user = require('../../user');
var meta = require('../../meta');
var db = require('../../database');
var pagination = require('../../pagination');
var events = require('../../events');
var plugins = require('../../plugins');
 
var usersController = {};
 
var userFields = ['uid', 'username', 'userslug', 'email', 'postcount', 'joindate', 'banned',
	'reputation', 'picture', 'flags', 'lastonline', 'email:confirmed'];
 
usersController.search = function (req, res, next) {
	res.render('admin/manage/users', {
		search_display: '',
		users: []
	});
};
 
usersController.sortByJoinDate = function (req, res, next) {
	getUsers('users:joindate', 'latest', undefined, undefined, req, res, next);
};
 
usersController.notValidated = function (req, res, next) {
	getUsers('users:notvalidated', 'notvalidated', undefined, undefined, req, res, next);
};
 
usersController.noPosts = function (req, res, next) {
	getUsers('users:postcount', 'noposts', '-inf', 0, req, res, next);
};
 
usersController.topPosters = function (req, res, next) {
	getUsers('users:postcount', 'topposts', 0, '+inf', req, res, next);
};
 
usersController.mostReputaion = function (req, res, next) {
	getUsers('users:reputation', 'mostreputation', 0, '+inf', req, res, next);
};
 
usersController.flagged = function (req, res, next) {
	getUsers('users:flags', 'mostflags', 1, '+inf', req, res, next);
};
 
usersController.inactive = function (req, res, next) {
	var timeRange = 1000 * 60 * 60 * 24 * 30 * (parseInt(req.query.months, 10) || 3);
	var cutoff = Date.now() - timeRange;
	getUsers('users:online', 'inactive', '-inf', cutoff, req, res, next);
};
 
usersController.banned = function (req, res, next) {
	getUsers('users:banned', 'banned', undefined, undefined, req, res, next);
};
 
usersController.registrationQueue = function (req, res, next) {
	var page = parseInt(req.query.page, 10) || 1;
	var itemsPerPage = 20;
	var start = (page - 1) * 20;
	var stop = start + itemsPerPage - 1;
	var invitations;
 
	async.parallel({
		registrationQueueCount: function (next) {
			db.sortedSetCard('registration:queue', next);
		},
		users: function (next) {
			user.getRegistrationQueue(start, stop, next);
		},
		customHeaders: function (next) {
			plugins.fireHook('filter:admin.registrationQueue.customHeaders', {headers: []}, next);
		},
		invites: function (next) {
			async.waterfall([
				function (next) {
					user.getAllInvites(next);
				},
				function (_invitations, next) {
					invitations = _invitations;
					async.map(invitations, function (invites, next) {
						user.getUserField(invites.uid, 'username', next);
					}, next);
				},
				function (usernames, next) {
					invitations.forEach(function (invites, index) {
						invites.username = usernames[index];
					});
					async.map(invitations, function (invites, next) {
						async.map(invites.invitations, user.getUsernameByEmail, next);
					}, next);
				},
				function (usernames, next) {
					invitations.forEach(function (invites, index) {
						invites.invitations = invites.invitations.map(function (email, i) {
							return {
								email: email,
								username: usernames[index][i] === '[[global:guest]]' ? '' : usernames[index][i]
							};
						});
					});
					next(null, invitations);
				}
			], next);
		}
	}, function (err, data) {
		if (err) {
			return next(err);
		}
		var pageCount = Math.max(1, Math.ceil(data.registrationQueueCount / itemsPerPage));
		data.pagination = pagination.create(page, pageCount);
		data.customHeaders = data.customHeaders.headers;
		res.render('admin/manage/registration', data);
	});
};
 
function getUsers(set, section, min, max, req, res, next) {
	var page = parseInt(req.query.page, 10) || 1;
	var resultsPerPage = 50;
	var start = Math.max(0, page - 1) * resultsPerPage;
	var stop = start + resultsPerPage - 1;
	var byScore = min !== undefined && max !== undefined;
 
	async.parallel({
		count: function (next) {
			if (byScore) {
				db.sortedSetCount(set, min, max, next);
			} else {
				db.sortedSetCard(set, next);
			}
		},
		users: function (next) {
			async.waterfall([
				function (next) {
					if (byScore) {
						db.getSortedSetRevRangeByScore(set, start, resultsPerPage, max, min, next);
					} else {
						user.getUidsFromSet(set, start, stop, next);
					}
				},
				function (uids, next) {
					user.getUsersWithFields(uids, userFields, req.uid, next);
				}
			], next);
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
 
		results.users = results.users.filter(function (user) {
			user.email = validator.escape(String(user.email || ''));
			return user && parseInt(user.uid, 10);
		});
		var data = {
			users: results.users,
			page: page,
			pageCount: Math.max(1, Math.ceil(results.count / resultsPerPage))
		};
		data[section] = true;
		render(req, res, data);
	});
}
 
function render(req, res, data) {
	data.search_display = 'hidden';
	data.pagination = pagination.create(data.page, data.pageCount, req.query);
	data.requireEmailConfirmation = parseInt(meta.config.requireEmailConfirmation, 10) === 1;
 
	var registrationType = meta.config.registrationType;
 
	data.inviteOnly = registrationType === 'invite-only' || registrationType === 'admin-invite-only';
	data.adminInviteOnly = registrationType === 'admin-invite-only';
 
	res.render('admin/manage/users', data);
}
 
usersController.getCSV = function (req, res, next) {
	events.log({
		type: 'getUsersCSV',
		uid: req.user.uid,
		ip: req.ip
	});
 
	user.getUsersCSV(function (err, data) {
		if (err) {
			return next(err);
		}
		res.attachment('users.csv');
		res.setHeader('Content-Type', 'text/csv');
		res.end(data);
	});
};
 
module.exports = usersController;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/widgets.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/controllers/admin/widgets.js

Statements: 42.86% (3 / 7)      Branches: 0% (0 / 2)      Functions: 0% (0 / 2)      Lines: 42.86% (3 / 7)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17    1   1                     1  
'use strict';
 
var widgetsController = {};
 
widgetsController.get = function (req, res, next) {
	require('../../widgets/admin').get(function (err, data) {
		if (err) {
			return next(err);
		}
 
		res.render('admin/extend/widgets', data);
	});
};
 
 
module.exports = widgetsController;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/

Statements: 15.51% (29 / 187)      Branches: 22% (22 / 100)      Functions: 6.25% (2 / 32)      Lines: 15.68% (29 / 185)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/database/
File Statements Branches Functions Lines
mongo.js 16.38% (19 / 116) 20.34% (12 / 59) 5% (1 / 20) 16.52% (19 / 115)
redis.js 14.08% (10 / 71) 24.39% (10 / 41) 8.33% (1 / 12) 14.29% (10 / 70)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo.js

Statements: 16.38% (19 / 116)      Branches: 20.34% (12 / 59)      Functions: 5% (1 / 20)      Lines: 16.52% (19 / 115)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239      1   1 1 1 1 1 1 1   1   1                                                           1 1   1                                                                                                                                                   1                                     1                             1           1                   1                                                                                                                       1            
 
'use strict';
 
(function (module) {
 
	var winston = require('winston');
	var async = require('async');
	var nconf = require('nconf');
	var session = require('express-session');
	var _ = require('underscore');
	var semver = require('semver');
	var db;
 
	_.mixin(require('underscore.deep'));
 
	module.questions = [
		{
			name: 'mongo:host',
			description: 'Host IP or address of your MongoDB instance',
			'default': nconf.get('mongo:host') || '127.0.0.1'
		},
		{
			name: 'mongo:port',
			description: 'Host port of your MongoDB instance',
			'default': nconf.get('mongo:port') || 27017
		},
		{
			name: 'mongo:username',
			description: 'MongoDB username',
			'default': nconf.get('mongo:username') || ''
		},
		{
			name: 'mongo:password',
			description: 'Password of your MongoDB database',
			hidden: true,
			default: nconf.get('mongo:password') || '',
			before: function (value) { value = value || nconf.get('mongo:password') || ''; return value; }
		},
		{
			name: "mongo:database",
			description: "MongoDB database name",
			'default': nconf.get('mongo:database') || 'nodebb'
		}
	];
 
	module.helpers = module.helpers || {};
	module.helpers.mongo = require('./mongo/helpers');
 
	module.init = function (callback) {
		callback = callback || function () {};
		var mongoClient;
		try {
			mongoClient = require('mongodb').MongoClient;
		} catch (err) {
			winston.error('Unable to initialize MongoDB! Is MongoDB installed? Error :' + err.message);
			return callback(err);
		}
 
		var usernamePassword = '';
		if (nconf.get('mongo:username') && nconf.get('mongo:password')) {
			usernamePassword = nconf.get('mongo:username') + ':' + encodeURIComponent(nconf.get('mongo:password')) + '@';
		}
 
		// Sensible defaults for Mongo, if not set
		if (!nconf.get('mongo:host')) {
			nconf.set('mongo:host', '127.0.0.1');
		}
		if (!nconf.get('mongo:port')) {
			nconf.set('mongo:port', 27017);
		}
		if (!nconf.get('mongo:database')) {
			nconf.set('mongo:database', 'nodebb');
		}
 
		var hosts = nconf.get('mongo:host').split(',');
		var ports = nconf.get('mongo:port').toString().split(',');
		var servers = [];
 
		for (var i = 0; i < hosts.length; i++) {
			servers.push(hosts[i] + ':' + ports[i]);
		}
 
		var connString = 'mongodb://' + usernamePassword + servers.join() + '/' + nconf.get('mongo:database');
 
		var connOptions = {
			server: {
				poolSize: parseInt(nconf.get('mongo:poolSize'), 10) || 10
			}
		};
 
		connOptions = _.deepExtend((nconf.get('mongo:options') || {}), connOptions);
 
		mongoClient.connect(connString, connOptions, function (err, _db) {
			if (err) {
				winston.error("NodeBB could not connect to your Mongo database. Mongo returned the following error: " + err.message);
				return callback(err);
			}
 
			db = _db;
 
			module.client = db;
 
			require('./mongo/main')(db, module);
			require('./mongo/hash')(db, module);
			require('./mongo/sets')(db, module);
			require('./mongo/sorted')(db, module);
			require('./mongo/list')(db, module);
 
			if (nconf.get('mongo:password') && nconf.get('mongo:username')) {
				db.authenticate(nconf.get('mongo:username'), nconf.get('mongo:password'), function (err) {
					if (err) {
						return callback(err);
					}
					createSessionStore();
					createIndices();
				});
			} else {
				winston.warn('You have no mongo password setup!');
				createSessionStore();
				createIndices();
			}
 
			function createSessionStore() {
				var sessionStore;
				if (nconf.get('redis')) {
					sessionStore = require('connect-redis')(session);
					var rdb = require('./redis');
					rdb.client = rdb.connect();
 
					module.sessionStore = new sessionStore({
						client: rdb.client,
						ttl: 60 * 60 * 24 * 14
					});
				} else if (nconf.get('mongo')) {
					sessionStore = require('connect-mongo')(session);
					module.sessionStore = new sessionStore({
						db: db
					});
				}
			}
 
			function createIndices() {
				winston.info('[database] Checking database indices.');
				async.series([
					async.apply(createIndex, 'objects', {_key: 1, score: -1}, {background: true}),
					async.apply(createIndex, 'objects', {_key: 1, value: -1}, {background: true, unique: true, sparse: true}),
					async.apply(createIndex, 'objects', {expireAt: 1}, {expireAfterSeconds: 0, background: true})
				], function (err) {
					if (err) {
						winston.error('Error creating index ' + err.message);
					}
					winston.info('[database] Checking database indices done!');
					callback(err);
				});
			}
 
			function createIndex(collection, index, options, callback) {
				db.collection(collection).createIndex(index, options, callback);
			}
		});
	};
 
	module.checkCompatibility = function (callback) {
		var mongoPkg = require.main.require('./node_modules/mongodb/package.json'),
			err = semver.lt(mongoPkg.version, '2.0.0') ? new Error('The `mongodb` package is out-of-date, please run `./nodebb setup` again.') : null;
 
		if (err) {
			err.stacktrace = false;
		}
		callback(err);
	};
 
	module.info = function (db, callback) {
		if (!db) {
			return callback();
		}
		async.parallel({
			serverStatus: function (next) {
				db.command({'serverStatus': 1}, next);
			},
			stats: function (next) {
				db.command({'dbStats': 1}, next);
			},
			listCollections: function (next) {
				db.listCollections().toArray(function (err, items) {
					if (err) {
						return next(err);
					}
					async.map(items, function (collection, next) {
						db.collection(collection.name).stats(next);
					}, next);
				});
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var stats = results.stats;
			var scale = 1024 * 1024;
 
			results.listCollections = results.listCollections.map(function (collectionInfo) {
				return {
					name: collectionInfo.ns,
					count: collectionInfo.count,
					size: collectionInfo.size,
					avgObjSize: collectionInfo.avgObjSize,
					storageSize: collectionInfo.storageSize,
					totalIndexSize: collectionInfo.totalIndexSize,
					indexSizes: collectionInfo.indexSizes
				};
			});
 
			stats.mem = results.serverStatus.mem;
			stats.collectionData = results.listCollections;
			stats.network = results.serverStatus.network;
			stats.raw = JSON.stringify(stats, null, 4);
 
			stats.avgObjSize = stats.avgObjSize.toFixed(2);
			stats.dataSize = (stats.dataSize / scale).toFixed(2);
			stats.storageSize = (stats.storageSize / scale).toFixed(2);
			stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(2) : 0;
			stats.indexSize = (stats.indexSize / scale).toFixed(2);
			stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1';
			stats.host = results.serverStatus.host;
			stats.version = results.serverStatus.version;
			stats.uptime = results.serverStatus.uptime;
			stats.mongo = true;
 
			callback(null, stats);
		});
	};
 
	module.close = function () {
		db.close();
	};
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/redis.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/redis.js

Statements: 14.08% (10 / 71)      Branches: 24.39% (10 / 41)      Functions: 8.33% (1 / 12)      Lines: 14.29% (10 / 70)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159    1   1               1                                                 1                                                         1                                                                                     1                             1       1                                                 1 1        
'use strict';
 
(function (module) {
 
	var winston = require('winston'),
		nconf = require('nconf'),
		semver = require('semver'),
		session = require('express-session'),
		redis,
		connectRedis,
		redisClient;
 
	module.questions = [
		{
			name: 'redis:host',
			description: 'Host IP or address of your Redis instance',
			'default': nconf.get('redis:host') || '127.0.0.1'
		},
		{
			name: 'redis:port',
			description: 'Host port of your Redis instance',
			'default': nconf.get('redis:port') || 6379
		},
		{
			name: 'redis:password',
			description: 'Password of your Redis database',
			hidden: true,
			default: nconf.get('redis:password') || '',
			before: function (value) { value = value || nconf.get('redis:password') || ''; return value; }
		},
		{
			name: "redis:database",
			description: "Which database to use (0..n)",
			'default': nconf.get('redis:database') || 0
		}
	];
 
	module.init = function (callback) {
		try {
			redis = require('redis');
			connectRedis = require('connect-redis')(session);
		} catch (err) {
			winston.error('Unable to initialize Redis! Is Redis installed? Error :' + err.message);
			process.exit();
		}
 
		redisClient = module.connect();
 
		module.client = redisClient;
 
		module.sessionStore = new connectRedis({
			client: redisClient,
			ttl: 60 * 60 * 24 * 14
		});
 
		require('./redis/main')(redisClient, module);
		require('./redis/hash')(redisClient, module);
		require('./redis/sets')(redisClient, module);
		require('./redis/sorted')(redisClient, module);
		require('./redis/list')(redisClient, module);
 
		if (typeof callback === 'function') {
			callback();
		}
	};
 
	module.connect = function (options) {
		var redis_socket_or_host = nconf.get('redis:host');
		var cxn;
 
		if (!redis) {
			redis = require('redis');
		}
 
		options = options || {};
		if (nconf.get('redis:password')) {
			options.auth_pass = nconf.get('redis:password');
		}
 
		if (redis_socket_or_host && redis_socket_or_host.indexOf('/') >= 0) {
			/* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */
			cxn = redis.createClient(nconf.get('redis:host'), options);
		} else {
			/* Else, connect over tcp/ip */
			cxn = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'), options);
		}
 
		cxn.on('error', function (err) {
			winston.error(err.stack);
			process.exit(1);
		});
 
		if (nconf.get('redis:password')) {
			cxn.auth(nconf.get('redis:password'));
		}
 
		var dbIdx = parseInt(nconf.get('redis:database'), 10);
		if (dbIdx) {
			cxn.select(dbIdx, function (error) {
				if(error) {
					winston.error("NodeBB could not connect to your Redis database. Redis returned the following error: " + error.message);
					process.exit();
				}
			});
		}
 
		return cxn;
	};
 
	module.checkCompatibility = function (callback) {
		module.info(module.client, function (err, info) {
			if (err) {
				return callback(err);
			}
 
			if (semver.lt(info.redis_version, '2.8.9')) {
				err = new Error('Your Redis version is not new enough to support NodeBB, please upgrade Redis to v2.8.9 or higher.');
				err.stacktrace = false;
			}
 
			callback(err);
		});
	};
 
	module.close = function () {
		redisClient.quit();
	};
 
	module.info = function (cxn, callback) {
		if (!cxn) {
			return callback();
		}
		cxn.info(function (err, data) {
			if (err) {
				return callback(err);
			}
 
			var lines = data.toString().split("\r\n").sort();
			var redisData = {};
			lines.forEach(function (line) {
				var parts = line.split(':');
				if (parts[1]) {
					redisData[parts[0]] = parts[1];
				}
			});
 
			redisData.raw = JSON.stringify(redisData, null, 4);
			redisData.redis = true;
 
			callback(null, redisData);
		});
	};
 
	module.helpers = module.helpers || {};
	module.helpers.redis = require('./redis/helpers');
}(exports));
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/

Statements: 2.72% (22 / 809)      Branches: 0% (0 / 523)      Functions: 0% (0 / 202)      Lines: 2.72% (22 / 809)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/
File Statements Branches Functions Lines
hash.js 0.71% (1 / 141) 0% (0 / 85) 0% (0 / 31) 0.71% (1 / 141)
helpers.js 30% (6 / 20) 0% (0 / 10) 0% (0 / 4) 30% (6 / 20)
list.js 1.79% (1 / 56) 0% (0 / 42) 0% (0 / 16) 1.79% (1 / 56)
main.js 3.57% (2 / 56) 0% (0 / 34) 0% (0 / 21) 3.57% (2 / 56)
sets.js 0.92% (1 / 109) 0% (0 / 71) 0% (0 / 35) 0.92% (1 / 109)
sorted.js 2.58% (11 / 427) 0% (0 / 281) 0% (0 / 95) 2.58% (11 / 427)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/hash.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/hash.js

Statements: 0.71% (1 / 141)      Branches: 0% (0 / 85)      Functions: 0% (0 / 31)      Lines: 0.71% (1 / 141)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248    1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
"use strict";
 
module.exports = function (db, module) {
	var helpers = module.helpers.mongo;
 
	module.setObject = function (key, data, callback) {
		callback = callback || helpers.noop;
		if (!key || !data) {
			return callback();
		}
 
		db.collection('objects').update({_key: key}, {$set: data}, {upsert: true, w: 1}, function (err) {
			callback(err);
		});
	};
 
	module.setObjectField = function (key, field, value, callback) {
		callback = callback || helpers.noop;
		if (!field) {
			return callback();
		}
		var data = {};
		field = helpers.fieldToString(field);
		data[field] = value;
		module.setObject(key, data, callback);
	};
 
	module.getObject = function (key, callback) {
		if (!key) {
			return callback();
		}
		db.collection('objects').findOne({_key: key}, {_id: 0, _key: 0}, callback);
	};
 
	module.getObjects = function (keys, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback(null, []);
		}
		db.collection('objects').find({_key: {$in: keys}}, {_id: 0}).toArray(function (err, data) {
			if (err) {
				return callback(err);
			}
 
			var map = helpers.toMap(data);
			var returnData = [];
 
			for (var i = 0; i < keys.length; ++i) {
				returnData.push(map[keys[i]]);
			}
 
			callback(null, returnData);
		});
	};
 
	module.getObjectField = function (key, field, callback) {
		if (!key) {
			return callback();
		}
		field = helpers.fieldToString(field);
		var _fields = {
			_id: 0
		};
		_fields[field] = 1;
		db.collection('objects').findOne({_key: key}, {fields: _fields}, function (err, item) {
			if (err || !item) {
				return callback(err, null);
			}
 
			callback(null, item.hasOwnProperty(field) ? item[field] : null);
		});
	};
 
	module.getObjectFields = function (key, fields, callback) {
		if (!key) {
			return callback();
		}
		var _fields = {
			_id: 0
		};
 
		for(var i = 0; i < fields.length; ++i) {
			fields[i] = helpers.fieldToString(fields[i]);
			_fields[fields[i]] = 1;
		}
		db.collection('objects').findOne({_key: key}, {fields: _fields}, function (err, item) {
			if (err) {
				return callback(err);
			}
			item = item || {};
			var result = {};
			for(i = 0; i < fields.length; ++i) {
				result[fields[i]] = item[fields[i]] !== undefined ? item[fields[i]] : null;
			}
			callback(null, result);
		});
	};
 
	module.getObjectsFields = function (keys, fields, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback(null, []);
		}
		var _fields = {
			_id: 0,
			_key: 1
		};
 
		for(var i = 0; i < fields.length; ++i) {
			fields[i] = helpers.fieldToString(fields[i]);
			_fields[fields[i]] = 1;
		}
 
		db.collection('objects').find({_key: {$in: keys}}, {fields: _fields}).toArray(function (err, items) {
			if (err) {
				return callback(err);
			}
 
			if (items === null) {
				items = [];
			}
 
			var map = helpers.toMap(items);
			var returnData = [];
			var item;
 
			for (var i = 0; i < keys.length; ++i) {
				item = map[keys[i]] || {};
 
				for (var k = 0; k < fields.length; ++k) {
					if (item[fields[k]] === undefined) {
						item[fields[k]] = null;
					}
				}
				returnData.push(item);
			}
 
			callback(null, returnData);
		});
	};
 
	module.getObjectKeys = function (key, callback) {
		module.getObject(key, function (err, data) {
			callback(err, data ? Object.keys(data) : []);
		});
	};
 
	module.getObjectValues = function (key, callback) {
		module.getObject(key, function (err, data) {
			if(err) {
				return callback(err);
			}
 
			var values = [];
			for(var key in data) {
				if (data && data.hasOwnProperty(key)) {
					values.push(data[key]);
				}
			}
			callback(null, values);
		});
	};
 
	module.isObjectField = function (key, field, callback) {
		if (!key) {
			return callback();
		}
		var data = {};
		field = helpers.fieldToString(field);
		data[field] = '';
		db.collection('objects').findOne({_key: key}, {fields: data}, function (err, item) {
			callback(err, !!item && item[field] !== undefined && item[field] !== null);
		});
	};
 
	module.isObjectFields = function (key, fields, callback) {
		if (!key) {
			return callback();
		}
 
		var data = {};
		fields.forEach(function (field) {
			field = helpers.fieldToString(field);
			data[field] = '';
		});
 
		db.collection('objects').findOne({_key: key}, {fields: data}, function (err, item) {
			if (err) {
				return callback(err);
			}
			var results = [];
 
			fields.forEach(function (field, index) {
				results[index] = !!item && item[field] !== undefined && item[field] !== null;
			});
 
			callback(null, results);
		});
	};
 
	module.deleteObjectField = function (key, field, callback) {
		module.deleteObjectFields(key, [field], callback);
	};
 
	module.deleteObjectFields = function (key, fields, callback) {
		callback = callback || helpers.noop;
		if (!key || !Array.isArray(fields) || !fields.length) {
			return callback();
		}
		fields = fields.filter(Boolean);
		if (!fields.length) {
			return callback();
		}
 
		var data = {};
		fields.forEach(function (field) {
			field = helpers.fieldToString(field);
			data[field] = '';
		});
 
		db.collection('objects').update({_key: key}, {$unset : data}, function (err) {
			callback(err);
		});
	};
 
	module.incrObjectField = function (key, field, callback) {
		module.incrObjectFieldBy(key, field, 1, callback);
	};
 
	module.decrObjectField = function (key, field, callback) {
		module.incrObjectFieldBy(key, field, -1, callback);
	};
 
	module.incrObjectFieldBy = function (key, field, value, callback) {
		callback = callback || helpers.noop;
		value = parseInt(value, 10);
		if (!key || isNaN(value)) {
			return callback();
		}
 
		var data = {};
		field = helpers.fieldToString(field);
		data[field] = value;
 
		db.collection('objects').findAndModify({_key: key}, {}, {$inc: data}, {new: true, upsert: true}, function (err, result) {
			callback(err, result && result.value ? result.value[field] : null);
		});
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/helpers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/helpers.js

Statements: 30% (6 / 20)      Branches: 0% (0 / 10)      Functions: 0% (0 / 4)      Lines: 30% (6 / 20)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38    1   1                 1                         1               1   1  
"use strict";
 
var helpers = {};
 
helpers.toMap = function (data) {
	var map = {};
	for (var i = 0; i < data.length; ++i) {
		map[data[i]._key] = data[i];
		data[i]._key = undefined;
	}
	return map;
};
 
helpers.fieldToString = function (field) {
	if(field === null || field === undefined) {
		return field;
	}
 
	if(typeof field !== 'string') {
		field = field.toString();
	}
	// if there is a '.' in the field name it inserts subdocument in mongo, replace '.'s with \uff0E
	field = field.replace(/\./g, '\uff0E');
	return field;
};
 
helpers.valueToString = function (value) {
	if(value === null || value === undefined) {
		return value;
	}
 
	return value.toString();
};
 
helpers.noop = function () {};
 
module.exports = helpers;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/list.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/list.js

Statements: 1.79% (1 / 56)      Branches: 0% (0 / 42)      Functions: 0% (0 / 16)      Lines: 1.79% (1 / 56)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104    1                                                                                                                                                                                                          
"use strict";
 
module.exports = function (db, module) {
	var helpers = module.helpers.mongo;
 
	module.listPrepend = function (key, value, callback) {
		callback = callback || helpers.noop;
 
		if (!key) {
			return callback();
		}
 
		value = helpers.valueToString(value);
 
		module.isObjectField(key, 'array', function (err, exists) {
			if (err) {
				return callback(err);
			}
 
			if (exists) {
				db.collection('objects').update({_key:key}, {$push: {array: {$each: [value], $position: 0}}}, {upsert:true, w:1 }, function (err, res) {
					callback(err);
				});
			} else {
				module.listAppend(key, value, callback);
			}
		});
	};
 
	module.listAppend = function (key, value, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		value = helpers.valueToString(value);
		db.collection('objects').update({ _key: key }, { $push: { array: value } }, {upsert:true, w:1}, function (err, res) {
			callback(err);
		});
	};
 
	module.listRemoveLast = function (key, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		module.getListRange(key, -1, -1, function (err, value) {
			if (err) {
				return callback(err);
			}
 
			db.collection('objects').update({_key: key }, { $pop: { array: 1 } }, function (err, result) {
				callback(err, (value && value.length) ? value[0] : null);
			});
		});
	};
 
	module.listRemoveAll = function (key, value, callback) {
		callback =  callback || helpers.noop;
		if (!key) {
			return callback();
		}
		value = helpers.valueToString(value);
 
		db.collection('objects').update({_key: key }, { $pull: { array: value } }, function (err, res) {
			callback(err);
		});
	};
 
	module.listTrim = function (key, start, stop, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		module.getListRange(key, start, stop, function (err, value) {
			if (err) {
				return callback(err);
			}
 
			db.collection('objects').update({_key: key}, {$set: {array: value}}, function (err, res) {
				callback(err);
			});
		});
	};
 
	module.getListRange = function (key, start, stop, callback) {
		if (!key) {
			return callback();
		}
 
		db.collection('objects').findOne({_key:key}, { array: 1}, function (err, data) {
			if(err || !(data && data.array)) {
				return callback(err, []);
			}
 
			if (stop === -1) {
				data.array = data.array.slice(start);
			} else {
				data.array = data.array.slice(start, stop + 1);
			}
			callback(null, data.array);
		});
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/main.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/main.js

Statements: 3.57% (2 / 56)      Branches: 0% (0 / 34)      Functions: 0% (0 / 21)      Lines: 3.57% (2 / 56)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100    1   1                                                                                                                                                                                              
"use strict";
 
var winston = require('winston');
 
module.exports = function (db, module) {
	var helpers = module.helpers.mongo;
 
	module.flushdb = function (callback) {
		callback = callback || helpers.noop;
		db.dropDatabase(function (err) {
			callback(err);
		});
	};
 
	module.emptydb = function (callback) {
		callback = callback || helpers.noop;
		db.collection('objects').remove({}, function (err) {
			callback(err);
		});
	};
 
	module.exists = function (key, callback) {
		if (!key) {
			return callback();
		}
		db.collection('objects').findOne({_key: key}, function (err, item) {
			callback(err, item !== undefined && item !== null);
		});
	};
 
	module.delete = function (key, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		db.collection('objects').remove({_key: key}, function (err, res) {
			callback(err);
		});
	};
 
	module.deleteAll = function (keys, callback) {
		callback = callback || helpers.noop;
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
		db.collection('objects').remove({_key: {$in: keys}}, function (err, res) {
			callback(err);
		});
	};
 
	module.get = function (key, callback) {
		if (!key) {
			return callback();
		}
		module.getObjectField(key, 'value', callback);
	};
 
	module.set = function (key, value, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		var data = {value: value};
		module.setObject(key, data, callback);
	};
 
	module.increment = function (key, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		db.collection('objects').findAndModify({_key: key}, {}, {$inc: {value: 1}}, {new: true, upsert: true}, function (err, result) {
			callback(err, result && result.value ? result.value.value : null);
		});
	};
 
	module.rename = function (oldKey, newKey, callback) {
		callback = callback || helpers.noop;
		db.collection('objects').update({_key: oldKey}, {$set:{_key: newKey}}, {multi: true}, function (err, res) {
			callback(err);
		});
	};
 
	module.expire = function (key, seconds, callback) {
		module.expireAt(key, Math.round(Date.now() / 1000) + seconds, callback);
	};
 
	module.expireAt = function (key, timestamp, callback) {
		module.setObjectField(key, 'expireAt', new Date(timestamp * 1000), callback);
	};
 
	module.pexpire = function (key, ms, callback) {
		module.pexpireAt(key, Date.now() + parseInt(ms, 10), callback);
	};
 
	module.pexpireAt = function (key, timestamp, callback) {
		module.setObjectField(key, 'expireAt', new Date(timestamp), callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/sets.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/sets.js

Statements: 0.92% (1 / 109)      Branches: 0% (0 / 71)      Functions: 0% (0 / 35)      Lines: 0.92% (1 / 109)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221    1                                                                                                                                                                                                                                                                                                                                                                                                                                                    
"use strict";
 
module.exports = function (db, module) {
	var helpers = module.helpers.mongo;
 
	module.setAdd = function (key, value, callback) {
		callback = callback || helpers.noop;
		if(!Array.isArray(value)) {
			value = [value];
		}
 
		value.forEach(function (element, index, array) {
			array[index] = helpers.valueToString(element);
		});
 
		db.collection('objects').update({
			_key: key
		}, {
			$addToSet: {
				members: {
					$each: value
				}
			}
		}, {
			upsert: true,
			w: 1
		}, function (err, res) {
			callback(err);
		});
	};
 
	module.setsAdd = function (keys, value, callback) {
		callback = callback || helpers.noop;
 
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
 
		if(!Array.isArray(value)) {
			value = [value];
		}
 
		value.forEach(function (element, index, array) {
			array[index] = helpers.valueToString(element);
		});
 
		var bulk = db.collection('objects').initializeUnorderedBulkOp();
 
		for(var i = 0; i < keys.length; ++i) {
			bulk.find({_key: keys[i]}).upsert().updateOne({	$addToSet: {
				members: {
					$each: value
				}
			}});
		}
 
		bulk.execute(function (err, res) {
			callback(err);
		});
	};
 
	module.setRemove = function (key, value, callback) {
		callback = callback || helpers.noop;
		if(!Array.isArray(value)) {
			value = [value];
		}
 
		value.forEach(function (element, index, array) {
			array[index] = helpers.valueToString(element);
		});
 
		db.collection('objects').update({_key: key}, {$pullAll: {members: value}}, function (err, res) {
			callback(err);
		});
	};
 
	module.setsRemove = function (keys, value, callback) {
		callback = callback || helpers.noop;
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
		value = helpers.valueToString(value);
 
		var bulk = db.collection('objects').initializeUnorderedBulkOp();
 
		for(var i = 0; i < keys.length; ++i) {
			bulk.find({_key: keys[i]}).updateOne({$pull: {
				members: value
			}});
		}
 
		bulk.execute(function (err, res) {
			callback(err);
		});
	};
 
	module.isSetMember = function (key, value, callback) {
		if (!key) {
			return callback(null, false);
		}
		value = helpers.valueToString(value);
 
		db.collection('objects').findOne({_key: key, members: value}, {_id: 0, members: 0},function (err, item) {
			callback(err, item !== null && item !== undefined);
		});
	};
 
	module.isSetMembers = function (key, values, callback) {
		if (!key || !Array.isArray(values) || !values.length) {
			return callback(null, []);
		}
 
		for (var i = 0; i < values.length; ++i) {
			values[i] = helpers.valueToString(values[i]);
		}
 
		db.collection('objects').findOne({_key: key}, {_id: 0, _key: 0}, function (err, items) {
			if (err) {
				return callback(err);
			}
 
			values = values.map(function (value) {
				return !!(items && Array.isArray(items.members) && items.members.indexOf(value) !== -1);
			});
 
			callback(null, values);
		});
	};
 
	module.isMemberOfSets = function (sets, value, callback) {
		if (!Array.isArray(sets) || !sets.length) {
			return callback(null, []);
		}
		value = helpers.valueToString(value);
 
		db.collection('objects').find({_key: {$in : sets}, members: value}, {_id:0, members: 0}).toArray(function (err, result) {
			if (err) {
				return callback(err);
			}
 
			result = result.map(function (item) {
				return item._key;
			});
 
			result = sets.map(function (set) {
				return result.indexOf(set) !== -1;
			});
 
			callback(null, result);
		});
	};
 
	module.getSetMembers = function (key, callback) {
		if (!key) {
			return callback(null, []);
		}
		db.collection('objects').findOne({_key: key}, {members: 1}, {_id: 0, _key: 0}, function (err, data) {
			callback(err, data ? data.members : []);
		});
	};
 
	module.getSetsMembers = function (keys, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback(null, []);
		}
		db.collection('objects').find({_key: {$in: keys}}, {_id: 0, _key: 1, members: 1}).toArray(function (err, data) {
			if (err) {
				return callback(err);
			}
 
			var sets = {};
			data.forEach(function (set) {
				sets[set._key] = set.members || [];
			});
 
			var returnData = new Array(keys.length);
			for(var i = 0; i < keys.length; ++i) {
				returnData[i] = sets[keys[i]] || [];
			}
			callback(null, returnData);
		});
	};
 
	module.setCount = function (key, callback) {
		if (!key) {
			return callback(null, 0);
		}
		db.collection('objects').findOne({_key: key}, {_id: 0}, function (err, data) {
			callback(err, data ? data.members.length : 0);
		});
	};
 
	module.setsCount = function (keys, callback) {
		module.getSetsMembers(keys, function (err, setsMembers) {
			if (err) {
				return callback(err);
			}
 
			var counts = setsMembers.map(function (members) {
				return (members && members.length) || 0;
			});
			callback(null, counts);
		});
	};
 
	module.setRemoveRandom = function (key, callback) {
		callback = callback || function () {};
		db.collection('objects').findOne({_key:key}, function (err, data) {
			if(err || !data) {
				return callback(err);
			}
 
			var randomIndex = Math.floor(Math.random() * data.members.length);
			var value = data.members[randomIndex];
			module.setRemove(data._key, value, function (err) {
				callback(err, value);
			});
		});
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/sorted.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/mongo/sorted.js

Statements: 2.58% (11 / 427)      Branches: 0% (0 / 281)      Functions: 0% (0 / 95)      Lines: 2.58% (11 / 427)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801    1 1   1                                           1                                                                               1                                                                                                                                 1                                                                                                           1                                                                                                                                                                                                                             1                                                                                                                                                                                                                                                                                                                                                                                                                                     1                                                                                                                                                                           1                                                                                                                                                                                                                                                                                   1                                                                                                                                        
"use strict";
 
var async = require('async');
var utils = require('../../../public/src/utils');
 
module.exports = function (db, module) {
	var helpers = module.helpers.mongo;
 
	module.sortedSetAdd = function (key, score, value, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		if (Array.isArray(score) && Array.isArray(value)) {
			return sortedSetAddBulk(key, score, value, callback);
		}
 
		value = helpers.valueToString(value);
 
		db.collection('objects').update({_key: key, value: value}, {$set: {score: parseInt(score, 10)}}, {upsert:true, w: 1}, function (err) {
			if (err && err.message.startsWith('E11000 duplicate key error')) {
				return process.nextTick(module.sortedSetAdd, key, score, value, callback);
			}
			callback(err);
		});
	};
 
	function sortedSetAddBulk(key, scores, values, callback) {
		if (!scores.length || !values.length) {
			return callback();
		}
		if (scores.length !== values.length) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		values = values.map(helpers.valueToString);
 
		var bulk = db.collection('objects').initializeUnorderedBulkOp();
 
		for(var i = 0; i < scores.length; ++i) {
			bulk.find({_key: key, value: values[i]}).upsert().updateOne({$set: {score: parseInt(scores[i], 10)}});
		}
 
		bulk.execute(function (err) {
			callback(err);
		});
	}
 
	module.sortedSetsAdd = function (keys, score, value, callback) {
		callback = callback || helpers.noop;
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
		value = helpers.valueToString(value);
 
		var bulk = db.collection('objects').initializeUnorderedBulkOp();
 
		for(var i = 0; i < keys.length; ++i) {
			bulk.find({_key: keys[i], value: value}).upsert().updateOne({$set: {score: parseInt(score, 10)}});
		}
 
		bulk.execute(function (err) {
			callback(err);
		});
	};
 
	module.sortedSetRemove = function (key, value, callback) {
		function done(err) {
			callback(err);
		}
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
 
		if (Array.isArray(value)) {
			value = value.map(helpers.valueToString);
			db.collection('objects').remove({_key: key, value: {$in: value}}, done);
		} else {
			value = helpers.valueToString(value);
			db.collection('objects').remove({_key: key, value: value}, done);
		}
	};
 
	module.sortedSetsRemove = function (keys, value, callback) {
		callback = callback || helpers.noop;
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
		value = helpers.valueToString(value);
 
		db.collection('objects').remove({_key: {$in: keys}, value: value}, function (err) {
			callback(err);
		});
	};
 
	module.sortedSetsRemoveRangeByScore = function (keys, min, max, callback) {
		callback = callback || helpers.noop;
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
		var query = {_key: {$in: keys}};
 
		if (min !== '-inf') {
			query.score = {$gte: min};
		}
		if (max !== '+inf') {
			query.score = query.score || {};
			query.score.$lte = max;
		}
 
		db.collection('objects').remove(query, function (err) {
			callback(err);
		});
	};
 
	module.getSortedSetRange = function (key, start, stop, callback) {
		getSortedSetRange(key, start, stop, 1, false, callback);
	};
 
	module.getSortedSetRevRange = function (key, start, stop, callback) {
		getSortedSetRange(key, start, stop, -1, false, callback);
	};
 
	module.getSortedSetRangeWithScores = function (key, start, stop, callback) {
		getSortedSetRange(key, start, stop, 1, true, callback);
	};
 
	module.getSortedSetRevRangeWithScores = function (key, start, stop, callback) {
		getSortedSetRange(key, start, stop, -1, true, callback);
	};
 
	function getSortedSetRange(key, start, stop, sort, withScores, callback) {
		if (!key) {
			return callback();
		}
 
		var fields = {_id: 0, value: 1};
		if (withScores) {
			fields.score = 1;
		}
 
		if (Array.isArray(key)) {
			key = {$in: key};
		}
 
		var limit = stop - start + 1;
		if (limit <= 0) {
			limit = 0;
		}
 
		db.collection('objects').find({_key: key}, {fields: fields})
			.limit(limit)
			.skip(start)
			.sort({score: sort})
			.toArray(function (err, data) {
				if (err || !data) {
					return callback(err);
				}
 
				if (!withScores) {
					data = data.map(function (item) {
						return item.value;
					});
				}
 
				callback(null, data);
			});
	}
 
	module.getSortedSetRangeByScore = function (key, start, count, min, max, callback) {
		getSortedSetRangeByScore(key, start, count, min, max, 1, false, callback);
	};
 
	module.getSortedSetRevRangeByScore = function (key, start, count, max, min, callback) {
		getSortedSetRangeByScore(key, start, count, min, max, -1, false, callback);
	};
 
	module.getSortedSetRangeByScoreWithScores = function (key, start, count, min, max, callback) {
		getSortedSetRangeByScore(key, start, count, min, max, 1, true, callback);
	};
 
	module.getSortedSetRevRangeByScoreWithScores = function (key, start, count, max, min, callback) {
		getSortedSetRangeByScore(key, start, count, min, max, -1, true, callback);
	};
 
	function getSortedSetRangeByScore(key, start, count, min, max, sort, withScores, callback) {
		if (!key) {
			return callback();
		}
		if(parseInt(count, 10) === -1) {
			count = 0;
		}
 
		var query = {_key: key};
 
		if (min !== '-inf') {
			query.score = {$gte: min};
		}
		if (max !== '+inf') {
			query.score = query.score || {};
			query.score.$lte = max;
		}
 
		var fields = {_id: 0, value: 1};
		if (withScores) {
			fields.score = 1;
		}
 
		db.collection('objects').find(query, {fields: fields})
			.limit(count)
			.skip(start)
			.sort({score: sort})
			.toArray(function (err, data) {
				if(err) {
					return callback(err);
				}
 
				if (!withScores) {
					data = data.map(function (item) {
						return item.value;
					});
				}
 
				callback(err, data);
			});
	}
 
	module.sortedSetCount = function (key, min, max, callback) {
		if (!key) {
			return callback();
		}
 
		var query = {_key: key};
		if (min !== '-inf') {
			query.score = {$gte: min};
		}
		if (max !== '+inf') {
			query.score = query.score || {};
			query.score.$lte = max;
		}
 
		db.collection('objects').count(query, function (err, count) {
			callback(err, count ? count : 0);
		});
	};
 
	module.sortedSetCard = function (key, callback) {
		if (!key) {
			return callback(null, 0);
		}
		db.collection('objects').count({_key: key}, function (err, count) {
			count = parseInt(count, 10);
			callback(err, count ? count : 0);
		});
	};
 
	module.sortedSetsCard = function (keys, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
		var pipeline = [
			{ $match : { _key : { $in: keys } } } ,
			{ $group: { _id: {_key: '$_key'}, count: { $sum: 1 } } },
			{ $project: { _id: 1, count: '$count' } }
		];
		db.collection('objects').aggregate(pipeline, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (!Array.isArray(results)) {
				results = [];
			}
 
			var map = {};
			results.forEach(function (item) {
				if (item && item._id._key) {
					map[item._id._key] = item.count;
				}
			});
 
			results = keys.map(function (key) {
				return map[key] || 0;
			});
			callback(null, results);
		});
	};
 
	module.sortedSetRank = function (key, value, callback) {
		getSortedSetRank(module.getSortedSetRange, key, value, callback);
	};
 
	module.sortedSetRevRank = function (key, value, callback) {
		getSortedSetRank(module.getSortedSetRevRange, key, value, callback);
	};
 
	function getSortedSetRank(method, key, value, callback) {
		if (!key) {
			return callback();
		}
		value = helpers.valueToString(value);
		method(key, 0, -1, function (err, result) {
			if(err) {
				return callback(err);
			}
 
			var rank = result.indexOf(value);
			callback(null, rank !== -1 ? rank : null);
		});
	}
 
	module.sortedSetsRanks = function (keys, values, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback(null, []);
		}
		var data = new Array(values.length);
		for (var i = 0; i < values.length; ++i) {
			data[i] = {key: keys[i], value: values[i]};
		}
 
		async.map(data, function (item, next) {
			getSortedSetRank(module.getSortedSetRange, item.key, item.value, next);
		}, callback);
	};
 
	module.sortedSetRanks = function (key, values, callback) {
		module.getSortedSetRange(key, 0, -1, function (err, sortedSet) {
			if (err) {
				return callback(err);
			}
 
			var result = values.map(function (value) {
				if (!value) {
					return null;
				}
				var index = sortedSet.indexOf(value.toString());
				return index !== -1 ? index : null;
			});
 
			callback(null, result);
		});
	};
 
	module.sortedSetScore = function (key, value, callback) {
		if (!key) {
			return callback();
		}
		value = helpers.valueToString(value);
		db.collection('objects').findOne({_key: key, value: value}, {fields:{_id: 0, score: 1}}, function (err, result) {
			callback(err, result ? result.score : null);
		});
	};
 
	module.sortedSetsScore = function (keys, value, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback();
		}
		value = helpers.valueToString(value);
		db.collection('objects').find({_key:{$in:keys}, value: value}, {_id:0, _key:1, score: 1}).toArray(function (err, result) {
			if (err) {
				return callback(err);
			}
 
			var map = helpers.toMap(result),
				returnData = [],
				item;
 
			for(var i = 0; i < keys.length; ++i) {
				item = map[keys[i]];
				returnData.push(item ? item.score : null);
			}
 
			callback(null, returnData);
		});
	};
 
	module.sortedSetScores = function (key, values, callback) {
		if (!key) {
			return callback();
		}
		values = values.map(helpers.valueToString);
		db.collection('objects').find({_key: key, value: {$in: values}}, {_id: 0, value: 1, score: 1}).toArray(function (err, result) {
			if (err) {
				return callback(err);
			}
 
			var map = {};
			result.forEach(function (item) {
				map[item.value] = item.score;
			});
 
			var returnData = new Array(values.length);
			var score;
 
			for(var i = 0; i < values.length; ++i) {
				score = map[values[i]];
				returnData[i] = utils.isNumber(score) ? score : null;
			}
 
			callback(null, returnData);
		});
	};
 
	module.isSortedSetMember = function (key, value, callback) {
		if (!key) {
			return callback();
		}
		value = helpers.valueToString(value);
		db.collection('objects').findOne({_key: key, value: value}, {_id: 0, value: 1}, function (err, result) {
			callback(err, !!result);
		});
	};
 
	module.isSortedSetMembers = function (key, values, callback) {
		if (!key) {
			return callback();
		}
		values = values.map(helpers.valueToString);
		db.collection('objects').find({_key: key, value: {$in: values}}, {fields: {_id: 0, value: 1}}).toArray(function (err, results) {
			if (err) {
				return callback(err);
			}
 
			results = results.map(function (item) {
				return item.value;
			});
 
			values = values.map(function (value) {
				return results.indexOf(value) !== -1;
			});
			callback(null, values);
		});
	};
 
	module.isMemberOfSortedSets = function (keys, value, callback) {
		if (!Array.isArray(keys)) {
			return callback();
		}
		value = helpers.valueToString(value);
		db.collection('objects').find({_key: {$in: keys}, value: value}, {fields: {_id: 0, _key: 1, value: 1}}).toArray(function (err, results) {
			if (err) {
				return callback(err);
			}
 
			results = results.map(function (item) {
				return item._key;
			});
 
			results = keys.map(function (key) {
				return results.indexOf(key) !== -1;
			});
			callback(null, results);
		});
	};
 
	module.getSortedSetsMembers = function (keys, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback(null, []);
		}
		db.collection('objects').find({_key: {$in: keys}}, {_id: 0, _key: 1, value: 1}).toArray(function (err, data) {
			if (err) {
				return callback(err);
			}
 
			var sets = {};
			data.forEach(function (set) {
			 	sets[set._key] = sets[set._key] || [];
			 	sets[set._key].push(set.value);
			});
 
			var returnData = new Array(keys.length);
			for(var i = 0; i < keys.length; ++i) {
			 	returnData[i] = sets[keys[i]] || [];
			}
			callback(null, returnData);
		});
	};
 
	module.sortedSetUnionCard = function (keys, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback(null, 0);
		}
 
		var pipeline = [
			{ $match: { _key: {$in: keys} } },
			{ $group: { _id: {value: '$value' } } },
			{ $group: { _id: null,  count: { $sum: 1 } } }
		];
 
		var project = { _id: 0, count: '$count' };
		pipeline.push({	$project: project });
 
		db.collection('objects').aggregate(pipeline, function (err, data) {
			callback(err, Array.isArray(data) && data.length ? data[0].count : 0);
		});
	};
 
	module.getSortedSetUnion = function (params, callback) {
		params.sort = 1;
		getSortedSetUnion(params, callback);
	};
 
	module.getSortedSetRevUnion = function (params, callback) {
		params.sort = -1;
		getSortedSetUnion(params, callback);
	};
 
	function getSortedSetUnion(params, callback) {
		if (!Array.isArray(params.sets) || !params.sets.length) {
			return callback();
		}
		var limit = params.stop - params.start + 1;
		if (limit <= 0) {
			limit = 0;
		}
 
		var aggregate = {};
		if (params.aggregate) {
			aggregate['$' + params.aggregate.toLowerCase()] = '$score';
		} else {
			aggregate.$sum = '$score';
		}
 
		var pipeline = [
			{ $match: { _key: {$in: params.sets}} },
			{ $group: { _id: {value: '$value'}, totalScore: aggregate} },
			{ $sort: { totalScore: params.sort} }
		];
 
		if (params.start) {
			pipeline.push({ $skip: params.start });
		}
 
		if (limit > 0) {
			pipeline.push({ $limit: limit });
		}
 
		var project = { _id: 0, value: '$_id.value' };
		if (params.withScores) {
			project.score = '$totalScore';
		}
		pipeline.push({	$project: project });
 
		db.collection('objects').aggregate(pipeline, function (err, data) {
			if (err || !data) {
				return callback(err);
			}
 
			if (!params.withScores) {
				data = data.map(function (item) {
					return item.value;
				});
			}
 
			callback(null, data);
		});
	}
 
	module.sortedSetIncrBy = function (key, increment, value, callback) {
		callback = callback || helpers.noop;
		if (!key) {
			return callback();
		}
		var data = {};
		value = helpers.valueToString(value);
		data.score = parseInt(increment, 10);
 
		db.collection('objects').findAndModify({_key: key, value: value}, {}, {$inc: data}, {new: true, upsert: true}, function (err, result) {
			// if there is duplicate key error retry the upsert
			// https://github.com/NodeBB/NodeBB/issues/4467
			// https://jira.mongodb.org/browse/SERVER-14322
			// https://docs.mongodb.org/manual/reference/command/findAndModify/#upsert-and-unique-index
			if (err && err.message.startsWith('E11000 duplicate key error')) {
				return process.nextTick(module.sortedSetIncrBy, key, increment, value, callback);
			}
			callback(err, result && result.value ? result.value.score : null);
		});
	};
 
	module.getSortedSetRangeByLex = function (key, min, max, start, count, callback) {
		sortedSetLex(key, min, max, 1, start, count, callback);
	};
 
	module.getSortedSetRevRangeByLex = function (key, max, min, start, count, callback) {
		sortedSetLex(key, min, max, -1, start, count, callback);
	};
 
	module.sortedSetLexCount = function (key, min, max, callback) {
		sortedSetLex(key, min, max, 1, 0, 0, function (err, data) {
			callback(err, data ? data.length : null);
		});
	};
 
	function sortedSetLex(key, min, max, sort, start, count, callback) {
		if (!callback) {
			callback = start;
			start = 0;
			count = 0;
		}
 
		var query = {_key: key};
 
		if (min !== '-') {
			if (min.match(/^\(/)) {
				query.value = {$gt: min.slice(1)};
			} else if (min.match(/^\[/)) {
				query.value = {$gte: min.slice(1)};
			} else {
				query.value = {$gte: min};
			}
		}
		if (max !== '+') {
			query.value = query.value || {};
			if (max.match(/^\(/)) {
				query.value.$lt = max.slice(1);
			} else if (max.match(/^\[/)) {
				query.value.$lte = max.slice(1);
			} else {
				query.value.$lte = max;
			}
		}
 
		db.collection('objects').find(query, {_id: 0, value: 1})
			.sort({value: sort})
			.skip(start)
			.limit(count === -1 ? 0 : count)
			.toArray(function (err, data) {
				if (err) {
					return callback(err);
				}
				data = data.map(function (item) {
					return item && item.value;
				});
				callback(err, data);
		});
	}
 
	module.sortedSetRemoveRangeByLex = function (key, min, max, callback) {
		callback = callback || helpers.noop;
 
		var query = {_key: key};
 
		if (min !== '-') {
			if (min.match(/^\(/)) {
				query.value = {$gt: min.slice(1)};
			} else if (min.match(/^\[/)) {
				query.value = {$gte: min.slice(1)};
			} else {
				query.value = {$gte: min};
			}
		}
		if (max !== '+') {
			query.value = query.value || {};
			if (max.match(/^\(/)) {
				query.value.$lt = max.slice(1);
			} else if (max.match(/^\[/)) {
				query.value.$lte = max.slice(1);
			} else {
				query.value.$lte = max;
			}
		}
 
		db.collection('objects').remove(query, function (err) {
			callback(err);
		});
	};
 
	module.processSortedSet = function (setKey, process, batch, callback) {
		var done = false;
		var ids = [];
		var cursor = db.collection('objects').find({_key: setKey})
			.sort({score: 1})
			.project({_id: 0, value: 1})
			.batchSize(batch);
 
		async.whilst(
			function () {
				return !done;
			},
			function (next) {
				cursor.next(function (err, item) {
					if (err) {
						return next(err);
					}
					if (item === null) {
						done = true;
					} else {
						ids.push(item.value);
					}
 
					if (ids.length < batch && (!done || ids.length === 0)) {
						return next(null);
					}
 
					process(ids, function (err) {
						ids = [];
						return next(err);
					});
				});
			},
			callback
		);
	};
 
	module.sortedSetIntersectCard = function (keys, callback) {
		if (!Array.isArray(keys) || !keys.length) {
			return callback(null, 0);
		}
 
		var pipeline = [
			{ $match: { _key: {$in: keys}} },
			{ $group: { _id: {value: '$value'}, count: {$sum: 1}} },
			{ $match: { count: keys.length} },
			{ $group: { _id: null,  count: { $sum: 1 } } }
		];
 
		db.collection('objects').aggregate(pipeline, function (err, data) {
			callback(err, Array.isArray(data) && data.length ? data[0].count : 0);
		});
	};
 
	module.getSortedSetIntersect = function (params, callback) {
		params.sort = 1;
		getSortedSetRevIntersect(params, callback);
	};
 
	module.getSortedSetRevIntersect = function (params, callback) {
		params.sort = -1;
		getSortedSetRevIntersect(params, callback);
	};
 
	function getSortedSetRevIntersect(params, callback) {
		var sets = params.sets;
		var start = params.hasOwnProperty('start') ? params.start : 0;
		var stop = params.hasOwnProperty('stop') ? params.stop : -1;
		var weights = params.weights || [];
		var aggregate = {};
 
		if (params.aggregate) {
			aggregate['$' + params.aggregate.toLowerCase()] = '$score';
		} else {
			aggregate.$sum = '$score';
		}
 
		var limit = stop - start + 1;
		if (limit <= 0) {
			limit = 0;
		}
 
		var pipeline = [{ $match: { _key: {$in: sets}} }];
 
		weights.forEach(function (weight, index) {
			if (weight !== 1) {
				pipeline.push({
					$project: {
						value: 1,
						score: {
							$cond: { if: { $eq: [ "$_key", sets[index] ] }, then: { $multiply: [ '$score', weight ] }, else: '$score' }
						}
					}
				});
			}
		});
 
		pipeline.push({ $group: { _id: {value: '$value'}, totalScore: aggregate, count: {$sum: 1}} });
		pipeline.push({ $match: { count: sets.length} });
		pipeline.push({ $sort: { totalScore: params.sort} });
 
		if (start) {
			pipeline.push({ $skip: start });
		}
 
		if (limit > 0) {
			pipeline.push({ $limit: limit });
		}
 
		var project = { _id: 0, value: '$_id.value'};
		if (params.withScores) {
			project.score = '$totalScore';
		}
		pipeline.push({ $project: project });
 
		db.collection('objects').aggregate(pipeline, function (err, data) {
			if (err || !data) {
				return callback(err);
			}
 
			if (!params.withScores) {
				data = data.map(function (item) {
					return item.value;
				});
			}
 
			callback(null, data);
		});
	}
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/redis/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/redis/

Statements: 25% (6 / 24)      Branches: 0% (0 / 6)      Functions: 0% (0 / 7)      Lines: 25% (6 / 24)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/database/redis/
File Statements Branches Functions Lines
helpers.js 25% (6 / 24) 0% (0 / 6) 0% (0 / 7) 25% (6 / 24)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/redis/helpers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/database/redis/helpers.js

Statements: 25% (6 / 24)      Branches: 0% (0 / 6)      Functions: 0% (0 / 7)      Lines: 25% (6 / 24)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40    1   1                 1                 1                 1             1  
"use strict";
 
var helpers = {};
 
helpers.multiKeys = function (redisClient, command, keys, callback) {
	callback = callback || function () {};
	var multi = redisClient.multi();
	for (var i = 0; i < keys.length; ++i) {
		multi[command](keys[i]);
	}
	multi.exec(callback);
};
 
helpers.multiKeysValue = function (redisClient, command, keys, value, callback) {
	callback = callback || function () {};
	var multi = redisClient.multi();
	for (var i = 0; i < keys.length; ++i) {
		multi[command](keys[i], value);
	}
	multi.exec(callback);
};
 
helpers.multiKeyValues = function (redisClient, command, key, values, callback) {
	callback = callback || function () {};
	var multi = redisClient.multi();
	for (var i = 0; i < values.length; ++i) {
		multi[command](key, values[i]);
	}
	multi.exec(callback);
};
 
helpers.resultsToBool = function (results) {
	for (var i = 0; i < results.length; ++i) {
		results[i] = results[i] === 1;
	}
	return results;
};
 
module.exports = helpers;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/

Statements: 6.3% (37 / 587)      Branches: 0% (0 / 320)      Functions: 0% (0 / 201)      Lines: 6.34% (37 / 584)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/groups/
File Statements Branches Functions Lines
cover.js 18.64% (11 / 59) 0% (0 / 18) 0% (0 / 21) 18.64% (11 / 59)
create.js 8.7% (4 / 46) 0% (0 / 44) 0% (0 / 9) 8.7% (4 / 46)
delete.js 8.7% (2 / 23) 0% (0 / 10) 0% (0 / 7) 8.7% (2 / 23)
membership.js 2.66% (7 / 263) 0% (0 / 130) 0% (0 / 95) 2.68% (7 / 261)
ownership.js 4.17% (1 / 24) 0% (0 / 6) 0% (0 / 10) 4.17% (1 / 24)
search.js 4.48% (3 / 67) 0% (0 / 31) 0% (0 / 23) 4.48% (3 / 67)
update.js 8.57% (9 / 105) 0% (0 / 81) 0% (0 / 36) 8.65% (9 / 104)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/cover.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/cover.js

Statements: 18.64% (11 / 59)      Branches: 0% (0 / 18)      Functions: 0% (0 / 21)      Lines: 18.64% (11 / 59)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131    2 2 2 2 2 2 2 2   2                                                                                                                                                               1                               1                                              
'use strict';
 
var async = require('async');
var nconf = require('nconf');
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
var Jimp = require('jimp');
var mime = require('mime');
var winston = require('winston');
 
var db = require('../database');
var file = require('../file');
var uploadsController = require('../controllers/uploads');
 
module.exports = function (Groups) {
 
	Groups.updateCoverPosition = function (groupName, position, callback) {
		if (!groupName) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		Groups.setGroupField(groupName, 'cover:position', position, callback);
	};
 
	Groups.updateCover = function (uid, data, callback) {
 
		// Position only? That's fine
		if (!data.imageData && data.position) {
			return Groups.updateCoverPosition(data.groupName, data.position, callback);
		}
 
		var tempPath = data.file ? data.file : '';
		var url;
		var type = data.file ? mime.lookup(data.file) : 'image/png';
 
		async.waterfall([
			function (next) {
				if (tempPath) {
					return next(null, tempPath);
				}
				writeImageDataToFile(data.imageData, next);
			},
			function (_tempPath, next) {
				tempPath = _tempPath;
				uploadsController.uploadGroupCover(uid, {
					name: 'groupCover',
					path: tempPath,
					type: type
				}, next);
			},
			function (uploadData, next) {
				url = uploadData.url;
				Groups.setGroupField(data.groupName, 'cover:url', url, next);
			},
			function (next) {
				resizeCover(tempPath, next);
			},
			function (next) {
				uploadsController.uploadGroupCover(uid, {
					name: 'groupCoverThumb',
					path: tempPath,
					type: type
				}, next);
			},
			function (uploadData, next) {
				Groups.setGroupField(data.groupName, 'cover:thumb:url', uploadData.url, next);
			},
			function (next) {
				fs.unlink(tempPath, next);	// Delete temporary file
			}
		], function (err) {
			if (err) {
				return fs.unlink(tempPath, function (unlinkErr) {
					if (unlinkErr) {
						winston.error(unlinkErr);
					}
 
					callback(err);	// send back original error
				});
			}
 
			if (data.position) {
				Groups.updateCoverPosition(data.groupName, data.position, function (err) {
					callback(err, {url: url});
				});
			} else {
				callback(err, {url: url});
			}
		});
	};
 
	function resizeCover(path, callback) {
		async.waterfall([
			function (next) {
				new Jimp(path, next);
			},
			function (image, next) {
				image.resize(358, Jimp.AUTO, next);
			},
			function (image, next) {
				image.write(path, next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	function writeImageDataToFile(imageData, callback) {
		// Calculate md5sum of image
		// This is required because user data can be private
		var md5sum = crypto.createHash('md5');
		md5sum.update(imageData);
		md5sum = md5sum.digest('hex');
 
		// Save image
		var tempPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), md5sum) + '.png';
		var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64');
 
		fs.writeFile(tempPath, buffer, {
			encoding: 'base64'
		}, function (err) {
			callback(err, tempPath);
		});
	}
 
	Groups.removeCover = function (data, callback) {
		db.deleteObjectFields('group:' + data.groupName, ['cover:url', 'cover:thumb:url'], callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/create.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/create.js

Statements: 8.7% (4 / 46)      Branches: 0% (0 / 44)      Functions: 0% (0 / 9)      Lines: 8.7% (4 / 46)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103    2 2                                                                                                                                                       1           1                                  
'use strict';
 
var async = require('async');
var meta = require('../meta');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
var db = require('../database');
 
module.exports = function (Groups) {
 
	Groups.create = function (data, callback) {
		var system = isSystemGroup(data);
		var groupData;
		var timestamp = data.timestamp || Date.now();
		var disableJoinRequests = parseInt(data.disableJoinRequests, 10) === 1 ? 1 : 0;
		if (data.name === 'administrators') {
			disableJoinRequests = 1;
		}
		async.waterfall([
			function (next) {
				validateGroupName(data.name, next);
			},
			function (next) {
				meta.userOrGroupExists(data.name, next);
			},
			function (exists, next) {
				if (exists) {
					return next(new Error('[[error:group-already-exists]]'));
				}
 
				var memberCount = data.hasOwnProperty('ownerUid') ? 1 : 0;
				var isPrivate = data.hasOwnProperty('private') ? parseInt(data.private, 10) : 1;
				var slug = utils.slugify(data.name);
				groupData = {
					name: data.name,
					slug: slug,
					createtime: timestamp,
					userTitle: data.userTitle || data.name,
					description: data.description || '',
					memberCount: memberCount,
					deleted: 0,
					hidden: parseInt(data.hidden, 10) === 1 ? 1 : 0,
					system: system ? 1 : 0,
					private: isPrivate,
					disableJoinRequests: disableJoinRequests
				};
				plugins.fireHook('filter:group.create', {group: groupData, data: data}, next);
			},
			function (results, next) {
				var tasks = [
					async.apply(db.sortedSetAdd, 'groups:createtime', groupData.createtime, groupData.name),
					async.apply(db.setObject, 'group:' + groupData.name, groupData)
				];
 
				if (data.hasOwnProperty('ownerUid')) {
					tasks.push(async.apply(db.setAdd, 'group:' + groupData.name + ':owners', data.ownerUid));
					tasks.push(async.apply(db.sortedSetAdd, 'group:' + groupData.name + ':members', timestamp, data.ownerUid));
 
					groupData.ownerUid = data.ownerUid;
				}
 
				if (!data.hidden && !system) {
					tasks.push(async.apply(db.sortedSetAdd, 'groups:visible:createtime', timestamp, groupData.name));
					tasks.push(async.apply(db.sortedSetAdd, 'groups:visible:memberCount', groupData.memberCount, groupData.name));
					tasks.push(async.apply(db.sortedSetAdd, 'groups:visible:name', 0, groupData.name.toLowerCase() + ':' + groupData.name));
				}
 
				tasks.push(async.apply(db.setObjectField, 'groupslug:groupname', groupData.slug, groupData.name));
 
				async.series(tasks, next);
			},
			function (results, next) {
				plugins.fireHook('action:group.create', groupData);
				next(null, groupData);
			}
		], callback);
 
	};
 
	function isSystemGroup(data) {
		return data.system === true || parseInt(data.system, 10) === 1 ||
			data.name === 'administrators' || data.name === 'registered-users' || data.name === 'Global Moderators' ||
			Groups.isPrivilegeGroup(data.name);
	}
 
	function validateGroupName(name, callback) {
		if (!name) {
			return callback(new Error('[[error:group-name-too-short]]'));
		}
 
		if (!Groups.isPrivilegeGroup(name) && name.length > (parseInt(meta.config.maximumGroupNameLength, 10) || 255)) {
			return callback(new Error('[[error:group-name-too-long]]'));
		}
 
		if (name.indexOf('/') !== -1 || !utils.slugify(name)) {
			return callback(new Error('[[error:invalid-group-name]]'));
		}
 
		callback();
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/delete.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/delete.js

Statements: 8.7% (2 / 23)      Branches: 0% (0 / 10)      Functions: 0% (0 / 7)      Lines: 8.7% (2 / 23)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54    2 2                                                                                                    
'use strict';
 
var async = require('async');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
var db = require('./../database');
 
module.exports = function (Groups) {
 
	Groups.destroy = function (groupName, callback) {
		Groups.getGroupsData([groupName], function (err, groupsData) {
			if (err) {
				return callback(err);
			}
			if (!Array.isArray(groupsData) || !groupsData[0]) {
				return callback();
			}
			var groupObj = groupsData[0];
 
			plugins.fireHook('action:group.destroy', groupObj);
 
			async.parallel([
				async.apply(db.delete, 'group:' + groupName),
				async.apply(db.sortedSetRemove, 'groups:createtime', groupName),
				async.apply(db.sortedSetRemove, 'groups:visible:createtime', groupName),
				async.apply(db.sortedSetRemove, 'groups:visible:memberCount', groupName),
				async.apply(db.sortedSetRemove, 'groups:visible:name', groupName.toLowerCase() + ':' + groupName),
				async.apply(db.delete, 'group:' + groupName + ':members'),
				async.apply(db.delete, 'group:' + groupName + ':pending'),
				async.apply(db.delete, 'group:' + groupName + ':invited'),
				async.apply(db.delete, 'group:' + groupName + ':owners'),
				async.apply(db.deleteObjectField, 'groupslug:groupname', utils.slugify(groupName)),
				function (next) {
					db.getSortedSetRange('groups:createtime', 0, -1, function (err, groups) {
						if (err) {
							return next(err);
						}
						async.each(groups, function (group, next) {
							db.sortedSetRemove('group:' + group + ':members', groupName, next);
						}, next);
					});
				}
			], function (err) {
				if (err) {
					return callback(err);
				}
				Groups.resetCache();
				callback();
			});
		});
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/membership.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/membership.js

Statements: 2.66% (7 / 263)      Branches: 0% (0 / 130)      Functions: 0% (0 / 95)      Lines: 2.68% (7 / 261)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568    2 2 2   2                                                                                                                                                                               1                                                                                                                                                         1                                                                                                                                                                                                                                                                                                       1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
'use strict';
 
var	async = require('async');
var winston = require('winston');
var _ = require('underscore');
 
var user = require('../user');
var utils = require('../../public/src/utils');
var plugins = require('../plugins');
var notifications = require('../notifications');
var db = require('../database');
 
var pubsub = require('../pubsub');
var LRU = require('lru-cache');
 
var cache = LRU({
	max: 40000,
	maxAge: 1000 * 60 * 60
});
 
module.exports = function (Groups) {
 
	Groups.cache = cache;
 
	Groups.join = function (groupName, uid, callback) {
		callback = callback || function () {};
 
		if (!groupName) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				Groups.isMember(uid, groupName, next);
			},
			function (isMember, next) {
				if (isMember) {
					return callback();
				}
				Groups.exists(groupName, next);
			},
			function (exists, next) {
				if (exists) {
					return next();
				}
				Groups.create({
					name: groupName,
					description: '',
					hidden: 1
				}, function (err) {
					if (err && err.message !== '[[error:group-already-exists]]') {
						winston.error('[groups.join] Could not create new hidden group: ' + err.message);
						return callback(err);
					}
					next();
				});
			},
			function (next) {
				async.parallel({
					isAdmin: function (next) {
						user.isAdministrator(uid, next);
					},
					isHidden: function (next) {
						Groups.isHidden(groupName, next);
					}
				}, next);
			},
			function (results, next) {
				var tasks = [
					async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', Date.now(), uid),
					async.apply(db.incrObjectField, 'group:' + groupName, 'memberCount')
				];
				if (results.isAdmin) {
					tasks.push(async.apply(db.setAdd, 'group:' + groupName + ':owners', uid));
				}
				if (!results.isHidden) {
					tasks.push(async.apply(db.sortedSetIncrBy, 'groups:visible:memberCount', 1, groupName));
				}
				async.parallel(tasks, next);
			},
			function (results, next) {
				clearCache(uid, groupName);
				setGroupTitleIfNotSet(groupName, uid, next);
			},
			function (next) {
				plugins.fireHook('action:group.join', {
					groupName: groupName,
					uid: uid
				});
				next();
			}
		], callback);
	};
 
	function setGroupTitleIfNotSet(groupName, uid, callback) {
		if (groupName === 'registered-users' || Groups.isPrivilegeGroup(groupName)) {
			return callback();
		}
 
		db.getObjectField('user:' + uid, 'groupTitle', function (err, currentTitle) {
			if (err || (currentTitle || currentTitle === '')) {
				return callback(err);
			}
 
			user.setUserField(uid, 'groupTitle', groupName, callback);
		});
	}
 
	Groups.requestMembership = function (groupName, uid, callback) {
		async.waterfall([
			async.apply(inviteOrRequestMembership, groupName, uid, 'request'),
			function (next) {
				user.getUserField(uid, 'username', next);
			},
			function (username, next) {
				async.parallel({
					notification: function (next) {
						notifications.create({
							bodyShort: '[[groups:request.notification_title, ' + username + ']]',
							bodyLong: '[[groups:request.notification_text, ' + username + ', ' + groupName + ']]',
							nid: 'group:' + groupName + ':uid:' + uid + ':request',
							path: '/groups/' + utils.slugify(groupName),
							from: uid
						}, next);
					},
					owners: function (next) {
						Groups.getOwners(groupName, next);
					}
				}, next);
			},
			function (results, next) {
				if (!results.notification || !results.owners.length) {
					return next();
				}
				notifications.push(results.notification, results.owners, next);
			}
		], callback);
	};
 
	Groups.acceptMembership = function (groupName, uid, callback) {
		// Note: For simplicity, this method intentially doesn't check the caller uid for ownership!
		async.waterfall([
			async.apply(db.setRemove, 'group:' + groupName + ':pending', uid),
			async.apply(db.setRemove, 'group:' + groupName + ':invited', uid),
			async.apply(Groups.join, groupName, uid)
		], callback);
	};
 
	Groups.rejectMembership = function (groupName, uid, callback) {
		// Note: For simplicity, this method intentially doesn't check the caller uid for ownership!
		async.parallel([
			async.apply(db.setRemove, 'group:' + groupName + ':pending', uid),
			async.apply(db.setRemove, 'group:' + groupName + ':invited', uid)
		], callback);
	};
 
	Groups.invite = function (groupName, uid, callback) {
		async.waterfall([
			async.apply(inviteOrRequestMembership, groupName, uid, 'invite'),
			async.apply(notifications.create, {
				bodyShort: '[[groups:invited.notification_title, ' + groupName + ']]',
				bodyLong: '',
				nid: 'group:' + groupName + ':uid:' + uid + ':invite',
				path: '/groups/' + utils.slugify(groupName)
			}),
			function (notification, next) {
				notifications.push(notification, [uid], next);
			}
		], callback);
	};
 
	function inviteOrRequestMembership(groupName, uid, type, callback) {
		if (!parseInt(uid, 10)) {
			return callback(new Error('[[error:not-logged-in]]'));
		}
		var hookName = type === 'invite' ? 'action:group.inviteMember' : 'action:group.requestMembership';
		var set = type === 'invite' ? 'group:' + groupName + ':invited' : 'group:' + groupName + ':pending';
 
		async.waterfall([
			function (next) {
				async.parallel({
					exists: async.apply(Groups.exists, groupName),
					isMember: async.apply(Groups.isMember, uid, groupName),
					isPending: async.apply(Groups.isPending, uid, groupName),
					isInvited: async.apply(Groups.isInvited, uid, groupName)
				}, next);
			},
			function (checks, next) {
				if (!checks.exists) {
					return next(new Error('[[error:no-group]]'));
				} else if (checks.isMember) {
					return callback();
				} else if (type === 'invite' && checks.isInvited) {
					return callback();
				} else if (type === 'request' && checks.isPending) {
					return next(new Error('[[error:group-already-requested]]'));
				}
 
				db.setAdd(set, uid, next);
			},
			function (next) {
				plugins.fireHook(hookName, {
					groupName: groupName,
					uid: uid
				});
				next();
			}
		], callback);
	}
 
	Groups.leave = function (groupName, uid, callback) {
		callback = callback || function () {};
 
		async.waterfall([
			function (next) {
				Groups.isMember(uid, groupName, next);
			},
			function (isMember, next) {
				if (!isMember) {
					return callback();
				}
 
				Groups.exists(groupName, next);
			},
			function (exists, next) {
				if (!exists) {
					return callback();
				}
				async.parallel([
					async.apply(db.sortedSetRemove, 'group:' + groupName + ':members', uid),
					async.apply(db.setRemove, 'group:' + groupName + ':owners', uid),
					async.apply(db.decrObjectField, 'group:' + groupName, 'memberCount')
				], next);
			},
			function (results, next) {
				clearCache(uid, groupName);
				Groups.getGroupFields(groupName, ['hidden', 'memberCount'], next);
			},
			function (groupData, next) {
				if (!groupData) {
					return callback();
				}
				if (Groups.isPrivilegeGroup(groupName) && parseInt(groupData.memberCount, 10) === 0) {
					Groups.destroy(groupName, next);
				} else {
					if (parseInt(groupData.hidden, 10) !== 1) {
						db.sortedSetAdd('groups:visible:memberCount', groupData.memberCount, groupName, next);
					} else {
						next();
					}
				}
			},
			function (next) {
				plugins.fireHook('action:group.leave', {
					groupName: groupName,
					uid: uid
				});
				next();
			}
		], callback);
	};
 
	Groups.leaveAllGroups = function (uid, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('groups:createtime', 0, -1, next);
			},
			function (groups, next) {
				async.each(groups, function (groupName, next) {
					async.parallel([
						function (next) {
							Groups.isMember(uid, groupName, function (err, isMember) {
								if (!err && isMember) {
									Groups.leave(groupName, uid, next);
								} else {
									next();
								}
							});
						},
						function (next) {
							Groups.rejectMembership(groupName, uid, next);
						}
					], next);
				}, next);
			}
		], callback);
	};
 
	Groups.getMembers = function (groupName, start, stop, callback) {
		db.getSortedSetRevRange('group:' + groupName + ':members', start, stop, callback);
	};
 
	Groups.getMemberUsers = function (groupNames, start, stop, callback) {
		async.map(groupNames, function (groupName, next) {
			Groups.getMembers(groupName, start, stop, function (err, uids) {
				if (err) {
					return next(err);
				}
 
				user.getUsersFields(uids, ['uid', 'username', 'picture', 'userslug'], next);
			});
		}, callback);
	};
 
	Groups.getMembersOfGroups = function (groupNames, callback) {
		db.getSortedSetsMembers(groupNames.map(function (name) {
			return 'group:' + name + ':members';
		}), callback);
	};
 
	Groups.resetCache = function () {
		pubsub.publish('group:cache:reset');
		cache.reset();
	};
 
	pubsub.on('group:cache:reset', function () {
		cache.reset();
	});
 
	function clearCache(uid, groupName) {
		pubsub.publish('group:cache:del', {uid: uid, groupName: groupName});
		cache.del(uid + ':' + groupName);
	}
 
	pubsub.on('group:cache:del', function (data) {
		cache.del(data.uid + ':' + data.groupName);
	});
 
	Groups.isMember = function (uid, groupName, callback) {
		if (!uid || parseInt(uid, 10) <= 0) {
			return callback(null, false);
		}
 
		var cacheKey = uid + ':' + groupName;
		if (cache.has(cacheKey)) {
			return process.nextTick(callback, null, cache.get(cacheKey));
		}
 
		db.isSortedSetMember('group:' + groupName + ':members', uid, function (err, isMember) {
			if (err) {
				return callback(err);
			}
 
			cache.set(cacheKey, isMember);
			callback(null, isMember);
		});
	};
 
	Groups.isMembers = function (uids, groupName, callback) {
		if (!groupName || !uids.length) {
			return callback(null, uids.map(function () {return false;}));
		}
 
		var nonCachedUids = [];
		uids.forEach(function (uid) {
			if (!cache.has(uid + ':' + groupName)) {
				nonCachedUids.push(uid);
			}
		});
 
		if (!nonCachedUids.length) {
			var result = uids.map(function (uid) {
				return cache.get(uid + ':' + groupName);
			});
			return process.nextTick(callback, null, result);
		}
 
		db.isSortedSetMembers('group:' + groupName + ':members', nonCachedUids, function (err, isMembers) {
			if (err) {
				return callback(err);
			}
 
			nonCachedUids.forEach(function (uid, index) {
				cache.set(uid + ':' + groupName, isMembers[index]);
			});
 
			var result = uids.map(function (uid) {
				return cache.get(uid + ':' + groupName);
			});
 
			callback(null, result);
		});
	};
 
	Groups.isMemberOfGroups = function (uid, groups, callback) {
		if (!uid || parseInt(uid, 10) <= 0 || !groups.length) {
			return callback(null, groups.map(function () {return false;}));
		}
 
		var nonCachedGroups = [];
 
		groups.forEach(function (groupName) {
			if (!cache.has(uid + ':' + groupName)) {
				nonCachedGroups.push(groupName);
			}
		});
 
		// are they all cached?
		if (!nonCachedGroups.length) {
			var result = groups.map(function (groupName) {
				return cache.get(uid + ':' + groupName);
			});
			return process.nextTick(callback, null, result);
		}
 
		var nonCachedGroupsMemberSets = nonCachedGroups.map(function (groupName) {
			return 'group:' + groupName + ':members';
		});
 
		db.isMemberOfSortedSets(nonCachedGroupsMemberSets, uid, function (err, isMembers) {
			if (err) {
				return callback(err);
			}
 
			nonCachedGroups.forEach(function (groupName, index) {
				cache.set(uid + ':' + groupName, isMembers[index]);
			});
 
			var result = groups.map(function (groupName) {
				return cache.get(uid + ':' + groupName);
			});
			callback(null, result);
		});
	};
 
	Groups.getMemberCount = function (groupName, callback) {
		db.getObjectField('group:' + groupName, 'memberCount', function (err, count) {
			if (err) {
				return callback(err);
			}
			callback(null, parseInt(count, 10));
		});
	};
 
	Groups.isMemberOfGroupList = function (uid, groupListKey, callback) {
		db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) {
			if (err) {
				return callback(err);
			}
			groupNames = Groups.internals.removeEphemeralGroups(groupNames);
			if (groupNames.length === 0) {
				return callback(null, false);
			}
 
			Groups.isMemberOfGroups(uid, groupNames, function (err, isMembers) {
				if (err) {
					return callback(err);
				}
 
				callback(null, isMembers.indexOf(true) !== -1);
			});
		});
	};
 
	Groups.isMemberOfGroupsList = function (uid, groupListKeys, callback) {
		var sets = groupListKeys.map(function (groupName) {
			return 'group:' + groupName + ':members';
		});
 
		db.getSortedSetsMembers(sets, function (err, members) {
			if (err) {
				return callback(err);
			}
 
			var uniqueGroups = _.unique(_.flatten(members));
			uniqueGroups = Groups.internals.removeEphemeralGroups(uniqueGroups);
 
			Groups.isMemberOfGroups(uid, uniqueGroups, function (err, isMembers) {
				if (err) {
					return callback(err);
				}
 
				var map = {};
 
				uniqueGroups.forEach(function (groupName, index) {
					map[groupName] = isMembers[index];
				});
 
				var result = members.map(function (groupNames) {
					for (var i = 0; i < groupNames.length; ++i) {
						if (map[groupNames[i]]) {
							return true;
						}
					}
					return false;
				});
 
				callback(null, result);
			});
		});
	};
 
	Groups.isMembersOfGroupList = function (uids, groupListKey, callback) {
		db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) {
			if (err) {
				return callback(err);
			}
 
			var results = [];
			uids.forEach(function () {
				results.push(false);
			});
 
			groupNames = Groups.internals.removeEphemeralGroups(groupNames);
			if (groupNames.length === 0) {
				return callback(null, results);
			}
 
			async.each(groupNames, function (groupName, next) {
				Groups.isMembers(uids, groupName, function (err, isMembers) {
					if (err) {
						return next(err);
					}
					results.forEach(function (isMember, index) {
						if (!isMember && isMembers[index]) {
							results[index] = true;
						}
					});
					next();
				});
			}, function (err) {
				callback(err, results);
			});
		});
	};
 
	Groups.isInvited = function (uid, groupName, callback) {
		if (!uid) {
			return callback(null, false);
		}
		db.isSetMember('group:' + groupName + ':invited', uid, callback);
	};
 
	Groups.isPending = function (uid, groupName, callback) {
		if (!uid) {
			return callback(null, false);
		}
		db.isSetMember('group:' + groupName + ':pending', uid, callback);
	};
 
	Groups.getPending = function (groupName, callback) {
		if (!groupName) {
			return callback(null, []);
		}
		db.getSetMembers('group:' + groupName + ':pending', callback);
	};
 
	Groups.kick = function (uid, groupName, isOwner, callback) {
		if (isOwner) {
			// If the owners set only contains one member, error out!
			async.waterfall([
				function (next) {
					db.setCount('group:' + groupName + ':owners', next);
				},
				function (numOwners, next) {
					if (numOwners <= 1) {
						return next(new Error('[[error:group-needs-owner]]'));
					}
					Groups.leave(groupName, uid, next);
				}
			], callback);
		} else {
			Groups.leave(groupName, uid, callback);
		}
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/ownership.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/ownership.js

Statements: 4.17% (1 / 24)      Branches: 0% (0 / 6)      Functions: 0% (0 / 10)      Lines: 4.17% (1 / 24)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61    2                                                                                                                    
'use strict';
 
var	async = require('async'),
	db = require('../database'),
	plugins = require('../plugins');
 
module.exports = function (Groups) {
 
	Groups.ownership = {};
 
	Groups.ownership.isOwner = function (uid, groupName, callback) {
		if (!uid) {
			return callback(null, false);
		}
		db.isSetMember('group:' + groupName + ':owners', uid, callback);
	};
 
	Groups.ownership.isOwners = function (uids, groupName, callback) {
		if (!Array.isArray(uids)) {
			return callback(null, []);
		}
 
		db.isSetMembers('group:' + groupName + ':owners', uids, callback);
	};
 
	Groups.ownership.grant = function (toUid, groupName, callback) {
		// Note: No ownership checking is done here on purpose!
		async.waterfall([
			function (next) {
				db.setAdd('group:' + groupName + ':owners', toUid, next);
			},
			function (next) {
				plugins.fireHook('action:group.grantOwnership', {uid: toUid, groupName: groupName});
				next();
			}
		], callback);
	};
 
	Groups.ownership.rescind = function (toUid, groupName, callback) {
		// Note: No ownership checking is done here on purpose!
 
		// If the owners set only contains one member, error out!
		async.waterfall([
			function (next) {
				db.setCount('group:' + groupName + ':owners', next);
			},
			function (numOwners, next) {
				if (numOwners <= 1) {
					return next(new Error('[[error:group-needs-owner]]'));
				}
				db.setRemove('group:' + groupName + ':owners', toUid, next);
			},
			function (next) {
				plugins.fireHook('action:group.rescindOwnership', {uid: toUid, groupName: groupName});
				next();
			}
		], callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/search.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/search.js

Statements: 4.48% (3 / 67)      Branches: 0% (0 / 31)      Functions: 0% (0 / 23)      Lines: 4.48% (3 / 67)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137    2   2                                                                                                                             1                                                                                                                                          
'use strict';
 
var async = require('async');
 
var user = require('../user');
var db = require('./../database');
 
 
module.exports = function (Groups) {
 
	Groups.search = function (query, options, callback) {
		if (!query) {
			return callback(null, []);
		}
		query = query.toLowerCase();
		async.waterfall([
			async.apply(db.getObjectValues, 'groupslug:groupname'),
			function (groupNames, next) {
				// Ephemeral groups and the registered-users groups are searchable
				groupNames = Groups.getEphemeralGroups().concat(groupNames).concat('registered-users');
				groupNames = groupNames.filter(function (name) {
					return name.toLowerCase().indexOf(query) !== -1 && name !== 'administrators' && !Groups.isPrivilegeGroup(name);
				});
				groupNames = groupNames.slice(0, 100);
				Groups.getGroupsData(groupNames, next);
			},
			function (groupsData, next) {
				groupsData = groupsData.filter(Boolean);
				if (options.filterHidden) {
					groupsData = groupsData.filter(function (group) {
						return !group.hidden;
					});
				}
 
				Groups.sort(options.sort, groupsData, next);
			}
		], callback);
	};
 
	Groups.sort = function (strategy, groups, next) {
		switch(strategy) {
			case 'count':
				groups = groups.sort(function (a, b) {
					return a.slug > b.slug;
				}).sort(function (a, b) {
					return b.memberCount - a.memberCount;
				});
				break;
 
			case 'date':
				groups = groups.sort(function (a, b) {
					return b.createtime - a.createtime;
				});
				break;
 
			case 'alpha':	// intentional fall-through
			default:
				groups = groups.sort(function (a, b) {
					return a.slug > b.slug ? 1 : -1;
				});
		}
 
		next(null, groups);
	};
 
	Groups.searchMembers = function (data, callback) {
 
		function findUids(query, searchBy, callback) {
			query = query.toLowerCase();
 
			async.waterfall([
				function (next) {
					Groups.getMembers(data.groupName, 0, -1, next);
				},
				function (members, next) {
					user.getUsersFields(members, ['uid'].concat([searchBy]), next);
				},
				function (users, next) {
					var uids = [];
					for(var i = 0; i < users.length; ++i) {
						var field = users[i][searchBy];
						if (field.toLowerCase().startsWith(query)) {
							uids.push(users[i].uid);
						}
					}
					next(null, uids);
				}
			], callback);
		}
 
		if (!data.query) {
			Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19, function (err, users) {
				if (err) {
					return callback(err);
				}
				callback(null, {users: users});
			});
			return;
		}
 
		data.findUids = findUids;
		var results;
		async.waterfall([
			function (next) {
				user.search(data, next);
			},
			function (_results, next) {
				results = _results;
				var uids = results.users.map(function (user) {
					return user && user.uid;
				});
				Groups.ownership.isOwners(uids, data.groupName, next);
			},
			function (isOwners, next) {
 
				results.users.forEach(function (user, index) {
					if (user) {
						user.isOwner = isOwners[index];
					}
				});
 
				results.users.sort(function (a,b) {
					if (a.isOwner && !b.isOwner) {
						return -1;
					} else if (!a.isOwner && b.isOwner) {
						return 1;
					} else {
						return 0;
					}
				});
				next(null, results);
			}
		], callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/update.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/groups/update.js

Statements: 8.57% (9 / 105)      Branches: 0% (0 / 81)      Functions: 0% (0 / 36)      Lines: 8.65% (9 / 104)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253    2 2   2                                                                                                                                                                 1                                                         1                     1                                                           1                                 1                                                                                                                 1                                            
'use strict';
 
var async = require('async');
var winston = require('winston');
 
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
var db = require('../database');
 
 
module.exports = function (Groups) {
 
	Groups.update = function (groupName, values, callback) {
		callback = callback || function () {};
 
		async.waterfall([
			function (next) {
				db.exists('group:' + groupName, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-group]]'));
				}
				plugins.fireHook('filter:group.update', {
					groupName: groupName,
					values: values
				}, next);
			},
			function (result, next) {
				values = result.values;
 
				var payload = {
					description: values.description || '',
					icon: values.icon || '',
					labelColor: values.labelColor || '#000000'
				};
 
				if (values.hasOwnProperty('userTitle')) {
					payload.userTitle = values.userTitle || '';
				}
 
				if (values.hasOwnProperty('userTitleEnabled')) {
					payload.userTitleEnabled = values.userTitleEnabled ? '1' : '0';
				}
 
				if (values.hasOwnProperty('hidden')) {
					payload.hidden = values.hidden ? '1' : '0';
				}
 
				if (values.hasOwnProperty('private')) {
					payload.private = values.private ? '1' : '0';
				}
 
				if (values.hasOwnProperty('disableJoinRequests')) {
					payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0';
				}
				async.series([
					async.apply(checkNameChange, groupName, values.name),
					function (next) {
						if (values.hasOwnProperty('private')) {
							updatePrivacy(groupName, values.private, next);
						} else {
							next();
						}
					},
					function (next) {
						if (values.hasOwnProperty('hidden')) {
							updateVisibility(groupName, values.hidden, next);
						} else {
							next();
						}
					},
					async.apply(db.setObject, 'group:' + groupName, payload),
					async.apply(renameGroup, groupName, values.name)
				], next);
			},
			function (result, next) {
				plugins.fireHook('action:group.update', {
					name: groupName,
					values: values
				});
				next();
			}
		], callback);
	};
 
	function updateVisibility(groupName, hidden, callback) {
		if (hidden) {
			async.parallel([
				async.apply(db.sortedSetRemove, 'groups:visible:createtime', groupName),
				async.apply(db.sortedSetRemove, 'groups:visible:memberCount', groupName),
				async.apply(db.sortedSetRemove, 'groups:visible:name', groupName.toLowerCase() + ':' + groupName),
			], callback);
		} else {
			db.getObjectFields('group:' + groupName, ['createtime', 'memberCount'], function (err, groupData) {
				if (err) {
					return callback(err);
				}
				async.parallel([
					async.apply(db.sortedSetAdd, 'groups:visible:createtime', groupData.createtime, groupName),
					async.apply(db.sortedSetAdd, 'groups:visible:memberCount', groupData.memberCount, groupName),
					async.apply(db.sortedSetAdd, 'groups:visible:name', 0, groupName.toLowerCase() + ':' + groupName),
				], callback);
			});
		}
	}
 
	Groups.hide = function (groupName, callback) {
		showHide(groupName, 'hidden', callback);
	};
 
	Groups.show = function (groupName, callback) {
		showHide(groupName, 'show', callback);
	};
 
	function showHide(groupName, hidden, callback) {
		hidden = hidden === 'hidden';
		callback = callback || function () {};
		async.parallel([
			async.apply(db.setObjectField, 'group:' + groupName, 'hidden', hidden ? 1 : 0),
			async.apply(updateVisibility, groupName, hidden)
		], function (err) {
			callback(err);
		});
	}
 
	function updatePrivacy(groupName, isPrivate, callback) {
		async.waterfall([
			function (next) {
				Groups.getGroupFields(groupName, ['private'], next);
			},
			function (currentValue, next) {
				var currentlyPrivate = parseInt(currentValue.private, 10) === 1;
				if (!currentlyPrivate || currentlyPrivate === isPrivate)  {
					return callback();
				}
				db.getSetMembers('group:' + groupName + ':pending', next);
			},
			function (uids, next) {
				if (!uids.length) {
					return callback();
				}
				var now = Date.now();
				var scores = uids.map(function () { return now; });
 
				winston.verbose('[groups.update] Group is now public, automatically adding ' + uids.length + ' new members, who were pending prior.');
				async.series([
					async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', scores, uids),
					async.apply(db.delete, 'group:' + groupName + ':pending')
				], next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	function checkNameChange(currentName, newName, callback) {
		if (currentName === newName) {
			return callback();
		}
		var currentSlug = utils.slugify(currentName);
		var newSlug = utils.slugify(newName);
		if (currentSlug === newSlug) {
			return callback();
		}
		Groups.existsBySlug(newSlug, function (err, exists) {
			if (err || exists) {
				return callback(err || new Error('[[error:group-already-exists]]'));
			}
			callback();
		});
	}
 
	function renameGroup(oldName, newName, callback) {
		if (oldName === newName || !newName || newName.length === 0) {
			return callback();
		}
 
		db.getObject('group:' + oldName, function (err, group) {
			if (err || !group) {
				return callback(err);
			}
 
			if (parseInt(group.system, 10) === 1) {
				return callback();
			}
 
			Groups.exists(newName, function (err, exists) {
				if (err || exists) {
					return callback(err || new Error('[[error:group-already-exists]]'));
				}
 
				async.series([
					async.apply(db.setObjectField, 'group:' + oldName, 'name', newName),
					async.apply(db.setObjectField, 'group:' + oldName, 'slug', utils.slugify(newName)),
					async.apply(db.deleteObjectField, 'groupslug:groupname', group.slug),
					async.apply(db.setObjectField, 'groupslug:groupname', utils.slugify(newName), newName),
					function (next) {
						db.getSortedSetRange('groups:createtime', 0, -1, function (err, groups) {
							if (err) {
								return next(err);
							}
							async.each(groups, function (group, next) {
								renameGroupMember('group:' + group + ':members', oldName, newName, next);
							}, next);
						});
					},
					async.apply(db.rename, 'group:' + oldName, 'group:' + newName),
					async.apply(db.rename, 'group:' + oldName + ':members', 'group:' + newName + ':members'),
					async.apply(db.rename, 'group:' + oldName + ':owners', 'group:' + newName + ':owners'),
					async.apply(db.rename, 'group:' + oldName + ':pending', 'group:' + newName + ':pending'),
					async.apply(db.rename, 'group:' + oldName + ':invited', 'group:' + newName + ':invited'),
 
					async.apply(renameGroupMember, 'groups:createtime', oldName, newName),
					async.apply(renameGroupMember, 'groups:visible:createtime', oldName, newName),
					async.apply(renameGroupMember, 'groups:visible:memberCount', oldName, newName),
					async.apply(renameGroupMember, 'groups:visible:name', oldName.toLowerCase() + ':' + oldName, newName.toLowerCase() + ':' + newName),
					function (next) {
						plugins.fireHook('action:group.rename', {
							old: oldName,
							new: newName
						});
 
						next();
					}
				], callback);
			});
		});
	}
 
	function renameGroupMember(group, oldName, newName, callback) {
		db.isSortedSetMember(group, oldName, function (err, isMember) {
			if (err || !isMember) {
				return callback(err);
			}
			var score;
			async.waterfall([
				function (next) {
					db.sortedSetScore(group, oldName, next);
				},
				function (_score, next) {
					score = _score;
					db.sortedSetRemove(group, oldName, next);
				},
				function (next) {
					db.sortedSetAdd(group, score, newName, next);
				}
			], callback);
		});
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/

Statements: 6.36% (18 / 283)      Branches: 0% (0 / 101)      Functions: 0% (0 / 114)      Lines: 6.36% (18 / 283)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/messaging/
File Statements Branches Functions Lines
create.js 3.85% (2 / 52) 0% (0 / 16) 0% (0 / 20) 3.85% (2 / 52)
delete.js 16.67% (2 / 12) 0% (0 / 2) 0% (0 / 6) 16.67% (2 / 12)
edit.js 5.88% (2 / 34) 0% (0 / 14) 0% (0 / 13) 5.88% (2 / 34)
notifications.js 11.11% (6 / 54) 0% (0 / 29) 0% (0 / 17) 11.11% (6 / 54)
rooms.js 3.85% (4 / 104) 0% (0 / 32) 0% (0 / 48) 3.85% (4 / 104)
unread.js 7.41% (2 / 27) 0% (0 / 8) 0% (0 / 10) 7.41% (2 / 27)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/create.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/create.js

Statements: 3.85% (2 / 52)      Branches: 0% (0 / 16)      Functions: 0% (0 / 20)      Lines: 3.85% (2 / 52)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121    2   2                                                                                                                                                                                                                                        
'use strict';
 
var async = require('async');
 
var meta = require('../meta');
var plugins = require('../plugins');
var db = require('../database');
 
 
module.exports = function (Messaging) {
 
	Messaging.sendMessage = function (uid, roomId, content, timestamp, callback) {
		async.waterfall([
			function (next) {
				Messaging.checkContent(content, next);
			},
			function (next) {
				Messaging.isUserInRoom(uid, roomId, next);
			},
			function (inRoom, next) {
				if (!inRoom) {
					return next(new Error('[[error:not-allowed]]'));
				}
 
				Messaging.addMessage(uid, roomId, content, timestamp, next);
			}
		], callback);
	};
 
	Messaging.checkContent = function (content, callback) {
		if (!content) {
			return callback(new Error('[[error:invalid-chat-message]]'));
		}
 
		if (content.length > (meta.config.maximumChatMessageLength || 1000)) {
			return callback(new Error('[[error:chat-message-too-long]]'));
		}
		callback();
	};
 
	Messaging.addMessage = function (fromuid, roomId, content, timestamp, callback) {
		var mid;
		var message;
		var isNewSet;
 
		async.waterfall([
			function (next) {
				Messaging.checkContent(content, next);
			},
			function (next) {
				db.incrObjectField('global', 'nextMid', next);
			},
			function (_mid, next) {
				mid = _mid;
				message = {
					content: content,
					timestamp: timestamp,
					fromuid: fromuid,
					roomId: roomId
				};
 
				plugins.fireHook('filter:messaging.save', message, next);
			},
			function (message, next) {
				db.setObject('message:' + mid, message, next);
			},
			function (next) {
				Messaging.isNewSet(fromuid, roomId, timestamp, next);
			},
			function (_isNewSet, next) {
				isNewSet = _isNewSet;
				db.getSortedSetRange('chat:room:' + roomId + ':uids', 0, -1, next);
			},
			function (uids, next) {
				async.parallel([
					async.apply(Messaging.addRoomToUsers, roomId, uids, timestamp),
					async.apply(Messaging.addMessageToUsers, roomId, uids, mid, timestamp),
					async.apply(Messaging.markUnread, uids, roomId),
					async.apply(Messaging.addUsersToRoom, fromuid, [fromuid], roomId)
				], next);
			},
			function (results, next) {
				async.parallel({
					markRead: async.apply(Messaging.markRead, fromuid, roomId),
					messages: async.apply(Messaging.getMessagesData, [mid], fromuid, roomId, true)
				}, next);
			},
			function (results, next) {
				if (!results.messages || !results.messages[0]) {
					return next(null, null);
				}
 
				results.messages[0].newSet = isNewSet;
				results.messages[0].mid = mid;
				results.messages[0].roomId = roomId;
				next(null, results.messages[0]);
			}
		], callback);
	};
 
	Messaging.addRoomToUsers = function (roomId, uids, timestamp, callback) {
		if (!uids.length) {
			return callback();
		}
		var keys = uids.map(function (uid) {
			return 'uid:' + uid + ':chat:rooms';
		});
		db.sortedSetsAdd(keys, timestamp, roomId, callback);
	};
 
	Messaging.addMessageToUsers = function (roomId, uids, mid, timestamp, callback) {
		if (!uids.length) {
			return callback();
		}
		var keys = uids.map(function (uid) {
			return 'uid:' + uid + ':chat:room:' + roomId + ':mids';
		});
		db.sortedSetsAdd(keys, timestamp, mid, callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/delete.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/delete.js

Statements: 16.67% (2 / 12)      Branches: 0% (0 / 2)      Functions: 0% (0 / 6)      Lines: 16.67% (2 / 12)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28    2 2                                                
'use strict';
 
var async = require('async');
var db = require('../database');
 
module.exports = function (Messaging) {
 
	Messaging.deleteMessage = function (mid, roomId, callback) {
		async.waterfall([
			function (next) {
				Messaging.getUidsInRoom(roomId, 0, -1, next);
			},
			function (uids, next) {
				if (!uids.length) {
					return next();
				}
				var keys = uids.map(function (uid) {
					return 'uid:' + uid + ':chat:room:' + roomId + 'mids';
				});
				db.sortedSetsRemove(keys, roomId, next);
			},
			function (next) {
				db.delete('message:' + mid, next);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/edit.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/edit.js

Statements: 5.88% (2 / 34)      Branches: 0% (0 / 14)      Functions: 0% (0 / 13)      Lines: 5.88% (2 / 34)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83    2   2                                                                                                                                                            
'use strict';
 
var async = require('async');
 
var meta = require('../meta');
var user = require('../user');
 
var sockets = require('../socket.io');
 
 
module.exports = function (Messaging) {
 
	Messaging.editMessage = function (uid, mid, roomId, content, callback) {
		var uids;
		async.waterfall([
			function (next) {
				Messaging.getMessageField(mid, 'content', next);
			},
			function (raw, next) {
				if (raw === content) {
					return callback();
				}
 
				Messaging.setMessageFields(mid, {
					content: content,
					edited: Date.now()
				}, next);
			},
			function (next) {
				Messaging.getUidsInRoom(roomId, 0, -1, next);
			},
			function (_uids, next) {
				uids = _uids;
				Messaging.getMessagesData([mid], uid, roomId, true, next);
			},
			function (messages, next) {
				uids.forEach(function (uid) {
					sockets.in('uid_' + uid).emit('event:chats.edit', {
						messages: messages
					});
				});
				next();
			}
		], callback);
	};
 
	Messaging.canEdit = function (messageId, uid, callback) {
		if (parseInt(meta.config.disableChat, 10) === 1) {
			return callback(null, false);
		} else if (parseInt(meta.config.disableChatMessageEditing, 10) === 1) {
			return callback(null, false);
		}
 
		async.waterfall([
			function (next) {
				user.getUserFields(uid, ['banned', 'email:confirmed'], next);
			},
			function (userData, next) {
				if (parseInt(userData.banned, 10) === 1) {
					return callback(null, false);
				}
 
				if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) {
					return callback(null, false);
				}
 
				Messaging.getMessageField(messageId, 'fromuid', next);
			},
			function (fromUid, next) {
				if (parseInt(fromUid, 10) === parseInt(uid, 10)) {
					return callback(null, true);
				}
 
				user.isAdministrator(uid, next);
			},
			function (isAdmin, next) {
				next(null, isAdmin);
			}
		], callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/notifications.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/notifications.js

Statements: 11.11% (6 / 54)      Branches: 0% (0 / 29)      Functions: 0% (0 / 17)      Lines: 11.11% (6 / 54)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125    2 2 2   2                                                                                               1                                                           1                                                                                
'use strict';
 
var async = require('async');
var nconf = require('nconf');
var winston = require('winston');
 
var user = require('../user');
var emailer = require('../emailer');
var notifications = require('../notifications');
var meta = require('../meta');
var sockets = require('../socket.io');
 
module.exports = function (Messaging) {
 
	Messaging.notifyQueue = {};	// Only used to notify a user of a new chat message, see Messaging.notifyUser
 
	Messaging.notifyUsersInRoom = function (fromUid, roomId, messageObj) {
		Messaging.getUidsInRoom(roomId, 0, -1, function (err, uids) {
			if (err) {
				return;
			}
 
			var data = {
				roomId: roomId,
				fromUid: fromUid,
				message: messageObj
			};
			uids.forEach(function (uid) {
				data.self = parseInt(uid, 10) === parseInt(fromUid) ? 1 : 0;
				Messaging.pushUnreadCount(uid);
				sockets.in('uid_' + uid).emit('event:chats.receive', data);
			});
 
			// Delayed notifications
			var queueObj = Messaging.notifyQueue[fromUid + ':' + roomId];
			if (queueObj) {
				queueObj.message.content += '\n' + messageObj.content;
				clearTimeout(queueObj.timeout);
			} else {
				queueObj = Messaging.notifyQueue[fromUid + ':' + roomId] = {
					message: messageObj
				};
			}
 
			queueObj.timeout = setTimeout(function () {
				sendNotifications(fromUid, uids, roomId, queueObj.message, function (err) {
					if (!err) {
						delete Messaging.notifyQueue[fromUid + ':' + roomId];
					}
				});
			}, 1000 * 60); // wait 60s before sending
		});
	};
 
	function sendNotifications(fromuid, uids, roomId, messageObj, callback) {
		user.isOnline(uids, function (err, isOnline) {
			if (err) {
				return callback(err);
			}
 
			uids = uids.filter(function (uid, index) {
				return !isOnline[index] && parseInt(fromuid, 10) !== parseInt(uid, 10);
			});
 
			if (!uids.length) {
				return callback();
			}
 
			notifications.create({
				bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
				bodyLong: messageObj.content,
				nid: 'chat_' + fromuid + '_' + roomId,
				from: fromuid,
				path: '/chats/' + messageObj.roomId
			}, function (err, notification) {
				if (!err && notification) {
					notifications.push(notification, uids, callback);
				}
			});
 
			sendNotificationEmails(uids, messageObj);
		});
	}
 
	function sendNotificationEmails(uids, messageObj) {
		if (parseInt(meta.config.disableEmailSubscriptions, 10) === 1) {
			return;
		}
 
		async.parallel({
			userData: function (next) {
				user.getUsersFields(uids, ['uid', 'username', 'userslug'], next);
			},
			userSettings: function (next) {
				user.getMultipleUserSettings(uids, next);
			}
		}, function (err, results) {
			if (err) {
				return winston.error(err);
			}
 
			results.userData = results.userData.filter(function (userData, index) {
				return userData && results.userSettings[index] && results.userSettings[index].sendChatNotifications;
			});
 
			async.each(results.userData, function (userData, next) {
				emailer.send('notif_chat', userData.uid, {
					subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]',
					summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
					message: messageObj,
					site_title: meta.config.title || 'NodeBB',
					url: nconf.get('url'),
					roomId: messageObj.roomId,
					username: userData.username,
					userslug: userData.userslug
				}, next);
			}, function (err) {
				if (err) {
					winston.error(err);
				}
			});
		});
	}
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/rooms.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/rooms.js

Statements: 3.85% (4 / 104)      Branches: 0% (0 / 32)      Functions: 0% (0 / 48)      Lines: 3.85% (4 / 104)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227    2 2   2                                                         1                                                                                                                                                                                                                                                                                                                                                                                                
'use strict';
 
var async = require('async');
var validator = require('validator');
 
var db = require('../database');
var user = require('../user');
var plugins = require('../plugins');
 
module.exports = function (Messaging) {
 
	Messaging.getRoomData = function (roomId, callback) {
		db.getObject('chat:room:' + roomId, function (err, data) {
			if (err || !data) {
				return callback(err || new Error('[[error:no-chat-room]]'));
			}
			modifyRoomData([data]);
			callback(null, data);
		});
	};
 
	Messaging.getRoomsData = function (roomIds, callback) {
		var keys = roomIds.map(function (roomId) {
			return 'chat:room:' + roomId;
		});
		db.getObjects(keys, function (err, roomData) {
			if (err) {
				return callback(err);
			}
			modifyRoomData(roomData);
			callback(null, roomData);
		});
	};
 
	function modifyRoomData(rooms) {
		rooms.forEach(function (data) {
			if (data) {
				data.roomName = data.roomName || '';
				data.roomName = validator.escape(String(data.roomName));
				if (data.hasOwnProperty('groupChat')) {
					data.groupChat = parseInt(data.groupChat, 10) === 1;
				}
			}
		});
	}
 
	Messaging.newRoom = function (uid, toUids, callback) {
		var roomId;
		var now = Date.now();
		async.waterfall([
			function (next) {
				db.incrObjectField('global', 'nextChatRoomId', next);
			},
			function (_roomId, next) {
				roomId = _roomId;
				var room = {
					owner: uid,
					roomId: roomId
				};
				db.setObject('chat:room:' + roomId, room, next);
			},
			function (next) {
				db.sortedSetAdd('chat:room:' + roomId + ':uids', now, uid, next);
			},
			function (next) {
				Messaging.addUsersToRoom(uid, toUids, roomId, next);
			},
			function (next) {
				Messaging.addRoomToUsers(roomId, [uid].concat(toUids), now, next);
			},
			function (next) {
				next(null, roomId);
			}
		], callback);
	};
 
	Messaging.isUserInRoom = function (uid, roomId, callback) {
		async.waterfall([
			function (next) {
				db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, next);
			},
			function (inRoom, next) {
				plugins.fireHook('filter:messaging.isUserInRoom', {uid: uid, roomId: roomId, inRoom: inRoom}, next);
			},
			function (data, next) {
				next(null, data.inRoom);
			}
		], callback);
	};
 
	Messaging.roomExists = function (roomId, callback) {
		db.exists('chat:room:' + roomId + ':uids', callback);
	};
 
	Messaging.getUserCountInRoom = function (roomId, callback) {
		db.sortedSetCard('chat:room:' + roomId + ':uids', callback);
	};
 
	Messaging.isRoomOwner = function (uid, roomId, callback) {
		db.getObjectField('chat:room:' + roomId, 'owner', function (err, owner) {
			if (err) {
				return callback(err);
			}
 
			callback(null, parseInt(uid, 10) === parseInt(owner, 10));
		});
	};
 
	Messaging.addUsersToRoom = function (uid, uids, roomId, callback) {
		async.waterfall([
			function (next) {
				Messaging.isUserInRoom(uid, roomId, next);
			},
			function (inRoom, next) {
				if (!inRoom) {
					return next(new Error('[[error:cant-add-users-to-chat-room]]'));
				}
				var now = Date.now();
				var timestamps = uids.map(function () {
					return now;
				});
				db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamps, uids, next);
			},
			function (next) {
				async.parallel({
					userCount: async.apply(db.sortedSetCard, 'chat:room:' + roomId + ':uids'),
					roomData: async.apply(db.getObject, 'chat:room:' + roomId)
				}, next);
			},
			function (results, next) {
				if (!results.roomData.hasOwnProperty('groupChat') && results.userCount > 2) {
					return db.setObjectField('chat:room:' + roomId, 'groupChat', 1, next);
				}
				next();
			}
		], callback);
	};
 
	Messaging.removeUsersFromRoom = function (uid, uids, roomId, callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					isOwner: async.apply(Messaging.isRoomOwner, uid, roomId),
					userCount: async.apply(Messaging.getUserCountInRoom, roomId)
				}, next);
			},
			function (results, next) {
				if (!results.isOwner) {
					return next(new Error('[[error:cant-remove-users-from-chat-room]]'));
				}
				if (results.userCount === 2) {
					return next(new Error('[[error:cant-remove-last-user]]'));
				}
				Messaging.leaveRoom(uids, roomId, next);
			}
		], callback);
	};
 
	Messaging.leaveRoom = function (uids, roomId, callback) {
		async.waterfall([
			function (next) {
				db.sortedSetRemove('chat:room:' + roomId + ':uids', uids, next);
			},
			function (next) {
				var keys = uids.map(function (uid) {
					return 'uid:' + uid + ':chat:rooms';
				});
				keys = keys.concat(uids.map(function (uid) {
					return 'uid:' + uid + ':chat:rooms:unread';
				}));
				db.sortedSetsRemove(keys, roomId, next);
			}
		], callback);
	};
 
	Messaging.getUidsInRoom = function (roomId, start, stop, callback) {
		db.getSortedSetRevRange('chat:room:' + roomId + ':uids', start, stop, callback);
	};
 
	Messaging.getUsersInRoom = function (roomId, start, stop, callback) {
		async.waterfall([
			function (next) {
				Messaging.getUidsInRoom(roomId, start, stop, next);
			},
			function (uids, next) {
				user.getUsersFields(uids, ['uid', 'username', 'picture', 'status'], next);
			}
		], callback);
	};
 
	Messaging.renameRoom = function (uid, roomId, newName, callback) {
		if (!newName) {
			return callback(new Error('[[error:invalid-name]]'));
		}
		newName = newName.trim();
		if (newName.length > 75) {
			return callback(new Error('[[error:chat-room-name-too-long]]'));
		}
		async.waterfall([
			function (next) {
				Messaging.isRoomOwner(uid, roomId, next);
			},
			function (isOwner, next) {
				if (!isOwner) {
					return next(new Error('[[error:no-privileges]]'));
				}
				db.setObjectField('chat:room:' + roomId, 'roomName', newName, next);
			}
		], callback);
	};
 
	Messaging.canReply = function (roomId, uid, callback) {
		async.waterfall([
			function (next) {
				db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, next);
			},
			function (inRoom, next) {
				plugins.fireHook('filter:messaging.canReply', {uid: uid, roomId: roomId, inRoom: inRoom, canReply: inRoom}, next);
			},
			function (data, next) {
				next(null, data.canReply);
			}
		], callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/unread.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/messaging/unread.js

Statements: 7.41% (2 / 27)      Branches: 0% (0 / 8)      Functions: 0% (0 / 10)      Lines: 7.41% (2 / 27)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56    2   2                                                                                                      
'use strict';
 
var async = require('async');
 
var db = require('../database');
var sockets = require('../socket.io');
 
module.exports = function (Messaging) {
 
	Messaging.getUnreadCount = function (uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, 0);
		}
		db.sortedSetCard('uid:' + uid + ':chat:rooms:unread', callback);
	};
 
	Messaging.pushUnreadCount = function (uid) {
		if (!parseInt(uid, 10)) {
			return callback(null, 0);
		}
		Messaging.getUnreadCount(uid, function (err, unreadCount) {
			if (err) {
				return;
			}
			sockets.in('uid_' + uid).emit('event:unread.updateChatCount', unreadCount);
		});
	};
 
	Messaging.markRead = function (uid, roomId, callback) {
		db.sortedSetRemove('uid:' + uid + ':chat:rooms:unread', roomId, callback);
	};
 
	Messaging.markAllRead = function (uid, callback) {
		db.delete('uid:' + uid + ':chat:rooms:unread', callback);
	};
 
	Messaging.markUnread = function (uids, roomId, callback) {
		async.waterfall([
			function (next) {
				Messaging.roomExists(roomId, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:chat-room-does-not-exist]]'));
				}
				var keys = uids.map(function (uid) {
					return 'uid:' + uid + ':chat:rooms:unread';
				});
 
				db.sortedSetsAdd(keys, Date.now(), roomId, next);
			}
		], callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/

Statements: 11.24% (79 / 703)      Branches: 0% (0 / 300)      Functions: 0% (0 / 182)      Lines: 11.24% (79 / 703)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/meta/
File Statements Branches Functions Lines
blacklist.js 6.67% (4 / 60) 0% (0 / 29) 0% (0 / 10) 6.67% (4 / 60)
configs.js 7.32% (6 / 82) 0% (0 / 32) 0% (0 / 22) 7.32% (6 / 82)
css.js 12.26% (13 / 106) 0% (0 / 39) 0% (0 / 27) 12.26% (13 / 106)
dependencies.js 22.86% (8 / 35) 0% (0 / 20) 0% (0 / 5) 22.86% (8 / 35)
errors.js 10% (2 / 20) 0% (0 / 8) 0% (0 / 7) 10% (2 / 20)
js.js 10.53% (10 / 95) 0% (0 / 34) 0% (0 / 23) 10.53% (10 / 95)
settings.js 3.23% (1 / 31) 0% (0 / 12) 0% (0 / 10) 3.23% (1 / 31)
sounds.js 11.39% (9 / 79) 0% (0 / 42) 0% (0 / 25) 11.39% (9 / 79)
tags.js 17.65% (6 / 34) 0% (0 / 28) 0% (0 / 8) 17.65% (6 / 34)
templates.js 16.25% (13 / 80) 0% (0 / 22) 0% (0 / 25) 16.25% (13 / 80)
themes.js 8.64% (7 / 81) 0% (0 / 34) 0% (0 / 20) 8.64% (7 / 81)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/blacklist.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/blacklist.js

Statements: 6.67% (4 / 60)      Branches: 0% (0 / 29)      Functions: 0% (0 / 10)      Lines: 6.67% (4 / 60)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127    2 2 2 2                                                                                                                                                                                                                                                  
'use strict';
 
var ip = require('ip');
var winston = require('winston');
var async = require('async');
var db = require('../database');
 
var Blacklist = {
		_rules: []
	};
 
Blacklist.load = function (callback) {
	async.waterfall([
		async.apply(db.get, 'ip-blacklist-rules'),
		async.apply(Blacklist.validate)
	], function (err, rules) {
		if (err) {
			return callback(err);
		}
 
		winston.verbose('[meta/blacklist] Loading ' + rules.valid.length + ' blacklist rules');
		if (rules.invalid.length) {
			winston.warn('[meta/blacklist] ' + rules.invalid.length + ' invalid blacklist rule(s) were ignored.');
		}
 
		Blacklist._rules = {
			ipv4: rules.ipv4,
			ipv6: rules.ipv6,
			cidr: rules.cidr
		};
 
		callback();
	});
};
 
Blacklist.save = function (rules, callback) {
	db.set('ip-blacklist-rules', rules, function (err) {
		if (err) {
			return callback(err);
		}
		Blacklist.load(callback);
	});
};
 
Blacklist.get = function (callback) {
	db.get('ip-blacklist-rules', callback);
};
 
Blacklist.test = function (clientIp, callback) {
	if (
		Blacklist._rules.ipv4.indexOf(clientIp) === -1	// not explicitly specified in ipv4 list
		&& Blacklist._rules.ipv6.indexOf(clientIp) === -1	// not explicitly specified in ipv6 list
		&& !Blacklist._rules.cidr.some(function (subnet) {
			return ip.cidrSubnet(subnet).contains(clientIp);
		})	// not in a blacklisted cidr range
	) {
		if (typeof callback === 'function') {
			callback();
		} else {
			return false;
		}
	} else {
		var err = new Error('[[error:blacklisted-ip]]');
		err.code = 'blacklisted-ip';
 
		if (typeof callback === 'function') {
			callback(err);
		} else {
			return true;
		}
	}
};
 
Blacklist.validate = function (rules, callback) {
	rules = (rules || '').split('\n');
	var ipv4 = [];
	var ipv6 = [];
	var cidr = [];
	var invalid = [];
 
	var isCidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/,
		inlineCommentMatch = /#.*$/,
		whitelist = ['127.0.0.1', '::1', '::ffff:0:127.0.0.1'];
 
	// Filter out blank lines and lines starting with the hash character (comments)
	// Also trim inputs and remove inline comments
	rules = rules.map(function (rule) {
		rule = rule.replace(inlineCommentMatch, '').trim();
		return rule.length && !rule.startsWith('#') ? rule : null;
	}).filter(Boolean);
 
	// Filter out invalid rules
	rules = rules.filter(function (rule) {
		if (whitelist.indexOf(rule) !== -1) {
			invalid.push(rule);
			return false;
		}
 
		if (ip.isV4Format(rule)) {
			ipv4.push(rule);
			return true;
		} else if (ip.isV6Format(rule)) {
			ipv6.push(rule);
			return true;
		} else if (isCidrSubnet.test(rule)) {
			cidr.push(rule);
			return true;
		} else {
			invalid.push(rule);
			return false;
		}
 
		return true;
	});
 
	callback(null, {
		numRules: rules.length + invalid.length,
		ipv4: ipv4,
		ipv6: ipv6,
		cidr: cidr,
		valid: rules,
		invalid: invalid
	});
};
 
module.exports = Blacklist;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/configs.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/configs.js

Statements: 7.32% (6 / 82)      Branches: 0% (0 / 32)      Functions: 0% (0 / 22)      Lines: 7.32% (6 / 82)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147      74 74   74                                                                                                                                                       1               1                           1                                                                                    
 
'use strict';
 
var winston = require('winston');
var nconf = require('nconf');
 
var db = require('../database');
var pubsub = require('../pubsub');
var utils = require('../../public/src/utils');
 
module.exports = function (Meta) {
 
	Meta.config = {};
	Meta.configs = {};
 
	Meta.configs.init = function (callback) {
		delete Meta.config;
 
		Meta.configs.list(function (err, config) {
			if (err) {
				winston.error(err.stack);
				return callback(err);
			}
 
			config['cache-buster'] = utils.generateUUID();
 
			Meta.config = config;
			callback();
		});
	};
 
	Meta.configs.list = function (callback) {
		db.getObject('config', function (err, config) {
			config = config || {};
			config.version = nconf.get('version');
			config.registry = nconf.get('registry');
			callback(err, config);
		});
	};
 
	Meta.configs.get = function (field, callback) {
		db.getObjectField('config', field, callback);
	};
 
	Meta.configs.getFields = function (fields, callback) {
		db.getObjectFields('config', fields, callback);
	};
 
	Meta.configs.set = function (field, value, callback) {
		callback = callback || function () {};
		if (!field) {
			return callback(new Error('invalid config field'));
		}
 
		db.setObjectField('config', field, value, function (err) {
			if (err) {
				return callback(err);
			}
			var data = {};
			data[field] = value;
			updateConfig(data);
 
			callback();
		});
	};
 
	Meta.configs.setMultiple = function (data, callback) {
		processConfig(data, function (err) {
			if (err) {
				return callback(err);
			}
			db.setObject('config', data, function (err) {
				if (err) {
					return callback(err);
				}
 
				updateConfig(data);
				callback();
			});
		});
	};
 
	function processConfig(data, callback) {
		if (data.customCSS) {
			saveRenderedCss(data, callback);
			return;
		}
		callback();
	}
 
	function saveRenderedCss(data, callback) {
		var less = require('less');
		less.render(data.customCSS, {
			compress: true
		}, function (err, lessObject) {
			if (err) {
				winston.error('[less] Could not convert custom LESS to CSS! Please check your syntax.');
				return callback(null, '');
			}
			data.renderedCustomCSS = lessObject.css;
			callback();
		});
	}
 
	function updateConfig(config) {
		pubsub.publish('config:update', config);
	}
 
	pubsub.on('config:update', function onConfigReceived(config) {
		if (typeof config !== 'object' || !Meta.config) {
			return;
		}
 
		for(var field in config) {
			if(config.hasOwnProperty(field)) {
				Meta.config[field] = config[field];
			}
		}
	});
 
	Meta.configs.setOnEmpty = function (values, callback) {
		db.getObject('config', function (err, data) {
			if (err) {
				return callback(err);
			}
			data = data || {};
			var empty = {};
			Object.keys(values).forEach(function (key) {
				if (!data.hasOwnProperty(key)) {
					empty[key] = values[key];
				}
			});
			if (Object.keys(empty).length) {
				db.setObject('config', empty, callback);
			} else {
				callback();
			}
		});
	};
 
	Meta.configs.remove = function (field) {
		db.deleteObjectField('config', field);
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/css.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/css.js

Statements: 12.26% (13 / 106)      Branches: 0% (0 / 39)      Functions: 0% (0 / 27)      Lines: 12.26% (13 / 106)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200    2 2 2 2 2 2 2 2 2   2                                                                                                                                                                       1                                                                                                                 1                                                                     1                      
'use strict';
 
var winston = require('winston');
var nconf = require('nconf');
var fs = require('fs');
var path = require('path');
var less = require('less');
var async = require('async');
var autoprefixer = require('autoprefixer');
var postcss = require('postcss');
var clean = require('postcss-clean');
 
var plugins = require('../plugins');
var db = require('../database');
var file = require('../file');
var utils = require('../../public/src/utils');
 
module.exports = function (Meta) {
 
	Meta.css = {};
	Meta.css.cache = undefined;
	Meta.css.acpCache = undefined;
 
	Meta.css.minify = function (target, callback) {
		callback = callback || function () {};
 
		winston.verbose('[meta/css] Minifying LESS/CSS');
		db.getObjectFields('config', ['theme:type', 'theme:id'], function (err, themeData) {
			if (err) {
				return callback(err);
			}
 
			var themeId = (themeData['theme:id'] || 'nodebb-theme-persona'),
				baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')),
				paths = [
					baseThemePath,
					path.join(__dirname, '../../node_modules'),
					path.join(__dirname, '../../public/vendor/fontawesome/less')
				],
				source = '@import "font-awesome";';
 
			plugins.lessFiles = filterMissingFiles(plugins.lessFiles);
			plugins.cssFiles = filterMissingFiles(plugins.cssFiles);
 
			async.waterfall([
				function (next) {
					getStyleSource(plugins.cssFiles, '\n@import (inline) ".', '.css', next);
				},
				function (src, next) {
					source += src;
					getStyleSource(plugins.lessFiles, '\n@import ".', '.less', next);
				},
				function (src, next) {
					source += src;
					next();
				}
			], function (err) {
				if (err) {
					return callback(err);
				}
 
				var acpSource = source;
 
				if (target !== 'admin.css') {
					source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/jquery/css/smoothness/jquery-ui.css";';
					source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css";';
					source += '\n@import (inline) "..' + path.sep + 'public/vendor/colorpicker/colorpicker.css";';
					source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/flags.less";';
					source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/blacklist.less";';
					source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/generics.less";';
					source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/mixins.less";';
					source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/global.less";';
					source = '@import "./theme";\n' + source;
 
					minify(source, paths, 'cache', callback);
				} else {
					acpSource += '\n@import "..' + path.sep + 'public/less/admin/admin";\n';
					acpSource += '\n@import "..' + path.sep + 'public/less/generics.less";\n';
					acpSource += '\n@import (inline) "..' + path.sep + 'public/vendor/colorpicker/colorpicker.css";\n';
					acpSource += '\n@import (inline) "..' + path.sep + 'public/vendor/jquery/css/smoothness/jquery-ui.css";';
 
					minify(acpSource, paths, 'acpCache', callback);
				}
			});
		});
	};
 
	Meta.css.getFromFile = function (callback) {
		async.series([
			async.apply(Meta.css.loadFile, path.join(__dirname, '../../public/stylesheet.css'), 'cache'),
			async.apply(Meta.css.loadFile, path.join(__dirname, '../../public/admin.css'), 'acpCache')
		], function (err) {
			callback(err);
		});
	};
 
	function getStyleSource(files, prefix, extension, callback) {
		var	pluginDirectories = [],
			source = '';
 
		files.forEach(function (styleFile) {
			if (styleFile.endsWith(extension)) {
				source += prefix + path.sep + styleFile + '";';
			} else {
				pluginDirectories.push(styleFile);
			}
		});
 
		async.each(pluginDirectories, function (directory, next) {
			utils.walk(directory, function (err, styleFiles) {
				if (err) {
					return next(err);
				}
 
				styleFiles.forEach(function (styleFile) {
					source += prefix + path.sep + styleFile + '";';
				});
 
				next();
			});
		}, function (err) {
			callback(err, source);
		});
	}
 
	Meta.css.commitToFile = function (filename, callback) {
		var file = (filename === 'acpCache' ? 'admin' : 'stylesheet') + '.css';
 
		fs.writeFile(path.join(__dirname, '../../public/' + file), Meta.css[filename], function (err) {
			if (!err) {
				winston.verbose('[meta/css] ' + file + ' committed to disk.');
			} else {
				winston.error('[meta/css] ' + err.message);
				process.exit(0);
			}
 
			callback();
		});
	};
 
	Meta.css.loadFile = function (filePath, filename, callback) {
		winston.verbose('[meta/css] Reading stylesheet ' + filePath.split('/').pop() + ' from file');
 
		fs.readFile(filePath, function (err, file) {
			if (err) {
				return callback(err);
			}
 
			Meta.css[filename] = file;
			callback();
		});
	};
 
	function minify(source, paths, destination, callback) {
		less.render(source, {
			paths: paths
		}, function (err, lessOutput) {
			if (err) {
				winston.error('[meta/css] Could not minify LESS/CSS: ' + err.message);
				if (typeof callback === 'function') {
					callback(err);
				}
				return;
			}
 
			postcss([ autoprefixer, clean() ]).process(lessOutput.css).then(function (result) {
				result.warnings().forEach(function (warn) {
					winston.verbose(warn.toString());
				});
				Meta.css[destination] = result.css;
 
				// Save the compiled CSS in public/ so things like nginx can serve it
				if (nconf.get('isPrimary') === 'true' && (nconf.get('local-assets') === undefined || nconf.get('local-assets') !== false)) {
					return Meta.css.commitToFile(destination, function () {
						if (typeof callback === 'function') {
							callback(null, result.css);
						}
					});
				}
 
				if (typeof callback === 'function') {
					callback(null, result.css);
				}
			});
 
		});
	}
 
	function filterMissingFiles(files) {
		return files.filter(function (filePath) {
			var exists = file.existsSync(path.join(__dirname, '../../node_modules', filePath));
			if (!exists) {
				winston.warn('[meta/css] File not found! ' + filePath);
			}
			return exists;
		});
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/dependencies.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/dependencies.js

Statements: 22.86% (8 / 35)      Branches: 0% (0 / 20)      Functions: 0% (0 / 5)      Lines: 22.86% (8 / 35)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61    1 1 1 1 1 1   1   1                                                                                                  
'use strict';
 
var path = require('path');
var fs = require('fs');
var async = require('async');
var semver = require('semver');
var winston = require('winston');
var colors = require('colors');
 
var pkg = require('../../package.json');
 
module.exports = function (Meta) {
	Meta.dependencies = {};
 
	Meta.dependencies.check = function (callback) {
		var modules = Object.keys(pkg.dependencies);
		var depsOutdated = false;
		var depsMissing = false;
 
		winston.verbose('Checking dependencies for outdated modules');
 
		async.every(modules, function (module, next) {
			fs.readFile(path.join(__dirname, '../../node_modules/', module, 'package.json'), {
				encoding: 'utf-8'
			}, function (err, pkgData) {
				// If a bundled plugin/theme is not present, skip the dep check (#3384)
				if (err && err.code === 'ENOENT' && (module === 'nodebb-rewards-essentials' || module.startsWith('nodebb-plugin') || module.startsWith('nodebb-theme'))) {
					winston.warn('[meta/dependencies] Bundled plugin ' + module + ' not found, skipping dependency check.');
					return next(true);
				}
 
				try {
					pkgData = JSON.parse(pkgData);
					var ok = !semver.validRange(pkg.dependencies[module]) || semver.satisfies(pkgData.version, pkg.dependencies[module]);
 
					if (ok || (pkgData._resolved && pkgData._resolved.indexOf('//github.com') !== -1)) {
						next(true);
					} else {
						process.stdout.write('[' + 'outdated'.yellow + '] ' + module.bold + ' installed v' + pkgData.version + ', package.json requires ' + pkg.dependencies[module] + '\n');
						depsOutdated = true;
						next(true);
					}
				} catch(e) {
					process.stdout.write('[' + 'missing'.red + '] ' + module.bold + ' is a required dependency but could not be found\n');
					depsMissing = true;
					next(true);
				}
			});
		}, function (ok) {
			if (depsMissing) {
				callback(new Error('dependencies-missing'));
			} else if (depsOutdated) {
				callback(global.env !== 'development' ? new Error('dependencies-out-of-date') : null);
			} else {
				callback(null);
			}
		});
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/errors.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/errors.js

Statements: 10% (2 / 20)      Branches: 0% (0 / 8)      Functions: 0% (0 / 7)      Lines: 10% (2 / 20)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39    2   2                                                                    
'use strict';
 
var validator = require('validator');
 
var db = require('../database');
var analytics = require('../analytics');
 
module.exports = function (Meta) {
 
	Meta.errors = {};
 
	Meta.errors.log404 = function (route, callback) {
		callback = callback || function () {};
		route = route.replace(/\/$/, '');	// remove trailing slashes
		analytics.increment('errors:404');
		db.sortedSetIncrBy('errors:404', 1, route, callback);
	};
 
	Meta.errors.get = function (escape, callback) {
		db.getSortedSetRevRangeWithScores('errors:404', 0, -1, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			data = data.map(function (nfObject) {
				nfObject.value = escape ? validator.escape(String(nfObject.value || '')) : nfObject.value;
				return nfObject;
			});
 
			callback(null, data);
		});
	};
 
	Meta.errors.clear = function (callback) {
		db.delete('errors:404', callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/js.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/js.js

Statements: 10.53% (10 / 95)      Branches: 0% (0 / 34)      Functions: 0% (0 / 23)      Lines: 10.53% (10 / 95)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268    2 2 2 2 2 2 2                                                                                                                                                                 1                                                                                                                                                                                                                                               1                                                                       1                                            
'use strict';
 
var winston = require('winston');
var fork = require('child_process').fork;
var path = require('path');
var async = require('async');
var nconf = require('nconf');
var fs = require('fs');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
 
module.exports = function (Meta) {
 
	Meta.js = {
		target: {},
		scripts: {
			base: [
				'./node_modules/jquery/dist/jquery.js',
				'./node_modules/socket.io-client/dist/socket.io.js',
				'public/vendor/jquery/timeago/jquery.timeago.js',
				'public/vendor/jquery/js/jquery.form.min.js',
				'public/vendor/visibility/visibility.min.js',
				'public/vendor/bootstrap/js/bootstrap.js',
				'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.min.js',
				'public/vendor/jquery/textcomplete/jquery.textcomplete.js',
				'public/vendor/requirejs/require.js',
				'public/src/require-config.js',
				'public/vendor/bootbox/bootbox.min.js',
				'public/vendor/tinycon/tinycon.js',
				'public/vendor/xregexp/xregexp.js',
				'public/vendor/xregexp/unicode/unicode-base.js',
				'./node_modules/templates.js/lib/templates.js',
				'public/src/utils.js',
				'public/src/sockets.js',
				'public/src/app.js',
				'public/src/ajaxify.js',
				'public/src/overrides.js',
				'public/src/widgets.js',
				"./node_modules/promise-polyfill/promise.js"
			],
 
			// files listed below are only available client-side, or are bundled in to reduce # of network requests on cold load
			rjs: [
				'public/src/client/footer.js',
				'public/src/client/chats.js',
				'public/src/client/infinitescroll.js',
				'public/src/client/pagination.js',
				'public/src/client/recent.js',
				'public/src/client/unread.js',
				'public/src/client/topic.js',
				'public/src/client/topic/events.js',
				'public/src/client/topic/flag.js',
				'public/src/client/topic/fork.js',
				'public/src/client/topic/move.js',
				'public/src/client/topic/posts.js',
				'public/src/client/topic/postTools.js',
				'public/src/client/topic/threadTools.js',
				'public/src/client/categories.js',
				'public/src/client/category.js',
				'public/src/client/categoryTools.js',
 
				'public/src/modules/translator.js',
				'public/src/modules/notifications.js',
				'public/src/modules/chat.js',
				'public/src/modules/components.js',
				'public/src/modules/sort.js',
				'public/src/modules/navigator.js',
				'public/src/modules/topicSelect.js',
				'public/src/modules/share.js',
				'public/src/modules/search.js',
				'public/src/modules/alerts.js',
				'public/src/modules/taskbar.js',
				'public/src/modules/helpers.js',
				'public/src/modules/sounds.js',
				'public/src/modules/string.js'
			],
 
			// modules listed below are routed through express (/src/modules) so they can be defined anonymously
			modules: {
				"Chart.js": './node_modules/chart.js/dist/Chart.min.js',
				"mousetrap.js": './node_modules/mousetrap/mousetrap.min.js',
				"jqueryui.js": 'public/vendor/jquery/js/jquery-ui.js',
				"buzz.js": 'public/vendor/buzz/buzz.js'
			}
		}
	};
 
	Meta.js.bridgeModules = function (app, callback) {
		// Add routes for AMD-type modules to serve those files
		function addRoute(relPath) {
			var relativePath = nconf.get('relative_path');
 
			app.get(relativePath + '/src/modules/' + relPath, function (req, res) {
				return res.sendFile(path.join(__dirname, '../../', Meta.js.scripts.modules[relPath]), {
					maxAge: app.enabled('cache') ? 5184000000 : 0
				});
			});
		}
 
		var numBridged = 0;
 
		for(var relPath in Meta.js.scripts.modules) {
			if (Meta.js.scripts.modules.hasOwnProperty(relPath)) {
				addRoute(relPath);
				++numBridged;
			}
		}
 
		winston.verbose('[meta/js] ' + numBridged + ' of ' + Object.keys(Meta.js.scripts.modules).length + ' modules bridged');
		callback();
	};
 
	Meta.js.minify = function (target, callback) {
		winston.verbose('[meta/js] Minifying ' + target);
 
		var forkProcessParams = setupDebugging();
		var minifier = Meta.js.minifierProc = fork('minifier.js', [], forkProcessParams);
 
		Meta.js.target[target] = {};
 
		Meta.js.prepare(target, function (err) {
			if (err) {
				return callback(err);
			}
			minifier.send({
				action: 'js',
				minify: global.env !== 'development',
				scripts: Meta.js.target[target].scripts
			});
		});
 
		minifier.on('message', function (message) {
			switch(message.type) {
			case 'end':
				Meta.js.target[target].cache = message.minified;
				Meta.js.target[target].map = message.sourceMap;
				winston.verbose('[meta/js] ' + target + ' minification complete');
				minifier.kill();
 
				if (nconf.get('local-assets') === undefined || nconf.get('local-assets') !== false) {
					return Meta.js.commitToFile(target, callback);
				} else {
					return callback();
				}
 
				break;
			case 'error':
				winston.error('[meta/js] Could not compile ' + target + ': ' + message.message);
				minifier.kill();
 
				callback(new Error(message.message));
				break;
			}
		});
	};
 
	Meta.js.prepare = function (target, callback) {
		var pluginsScripts = [];
 
		var pluginDirectories = [];
 
		pluginsScripts = plugins[target === 'nodebb.min.js' ? 'clientScripts' : 'acpScripts'].filter(function (path) {
			if (path.endsWith('.js')) {
				return true;
			}
 
			pluginDirectories.push(path);
			return false;
		});
 
		async.each(pluginDirectories, function (directory, next) {
			utils.walk(directory, function (err, scripts) {
				pluginsScripts = pluginsScripts.concat(scripts);
				next(err);
			});
		}, function (err) {
			if (err) {
				return callback(err);
			}
 
			var basePath = path.resolve(__dirname, '../..');
 
			Meta.js.target[target].scripts = Meta.js.scripts.base.concat(pluginsScripts);
 
			if (target === 'nodebb.min.js') {
				Meta.js.target[target].scripts = Meta.js.target[target].scripts.concat(Meta.js.scripts.rjs);
			}
 
			Meta.js.target[target].scripts = Meta.js.target[target].scripts.map(function (script) {
				return path.relative(basePath, script).replace(/\\/g, '/');
			});
 
			callback();
		});
	};
 
	Meta.js.killMinifier = function () {
		if (Meta.js.minifierProc) {
			Meta.js.minifierProc.kill('SIGTERM');
		}
	};
 
	Meta.js.commitToFile = function (target, callback) {
		fs.writeFile(path.join(__dirname, '../../public/' + target), Meta.js.target[target].cache, function (err) {
			callback(err);
		});
	};
 
	Meta.js.getFromFile = function (target, callback) {
		function readFile(filePath, next) {
			fs.readFile(filePath, function (err, contents) {
				if (err) {
					if (err.code === 'ENOENT') {
						if (!filePath.endsWith('.map')) {
							winston.warn('[meta/js] ' + filePath + ' not found on disk, did you run ./nodebb build?');
						}
						return next(null, '');
					}
				}
				next(err, contents);
			});
		}
 
		var scriptPath = path.join(nconf.get('base_dir'), 'public/' + target);
		var mapPath = path.join(nconf.get('base_dir'), 'public/' + target + '.map');
 
		async.parallel({
			script: function (next) {
				readFile(scriptPath, next);
			},
			map: function (next) {
				readFile(mapPath, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			Meta.js.target[target] = {
				cache: results.script,
				map: results.map
			};
			callback();
		});
	};
 
	function setupDebugging() {
		/**
		 * Check if the parent process is running with the debug option --debug (or --debug-brk)
		 */
		var forkProcessParams = {};
		if(global.v8debug || parseInt(process.execArgv.indexOf('--debug'), 10) !== -1) {
			/**
			 * use the line below if you want to debug minifier.js script too (or even --debug-brk option, but
			 * you'll have to setup your debugger and connect to the forked process)
			 */
			//forkProcessParams = {execArgv: ['--debug=' + (global.process.debugPort + 1), '--nolazy']};
 
			/**
			 * otherwise, just clean up --debug/--debug-brk options which are set up by default from the parent one
			 */
			forkProcessParams = {execArgv: []};
		}
 
		return forkProcessParams;
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/settings.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/settings.js

Statements: 3.23% (1 / 31)      Branches: 0% (0 / 12)      Functions: 0% (0 / 10)      Lines: 3.23% (1 / 31)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62    2                                                                                                                      
'use strict';
 
var db = require('../database');
var plugins = require('../plugins');
 
module.exports = function (Meta) {
 
	Meta.settings = {};
 
	Meta.settings.get = function (hash, callback) {
		db.getObject('settings:' + hash, function (err, settings) {
			callback(err, settings || {});
		});
	};
 
	Meta.settings.getOne = function (hash, field, callback) {
		db.getObjectField('settings:' + hash, field, callback);
	};
 
	Meta.settings.set = function (hash, values, callback) {
		var key = 'settings:' + hash;
		db.setObject(key, values, function (err) {
			if (err) {
				return callback(err);
			}
 
			plugins.fireHook('action:settings.set', {
				plugin: hash,
				settings: values
			});
 
			Meta.reloadRequired = true;
			callback();
		});
	};
 
	Meta.settings.setOne = function (hash, field, value, callback) {
		db.setObjectField('settings:' + hash, field, value, callback);
	};
 
	Meta.settings.setOnEmpty = function (hash, values, callback) {
		db.getObject('settings:' + hash, function (err, settings) {
			if (err) {
				return callback(err);
			}
			settings = settings || {};
			var empty = {};
			Object.keys(values).forEach(function (key) {
				if (!settings.hasOwnProperty(key)) {
					empty[key] = values[key];
				}
			});
 
			if (Object.keys(empty).length) {
				db.setObject('settings:' + hash, empty, callback);
			} else {
				callback();
			}
		});
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/sounds.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/sounds.js

Statements: 11.39% (9 / 79)      Branches: 0% (0 / 42)      Functions: 0% (0 / 25)      Lines: 11.39% (9 / 79)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163    2 2 2 2 2 2 2   2                                                                                                                                                             1                                                                                                                                                  
'use strict';
 
var path = require('path');
var fs = require('fs');
var nconf = require('nconf');
var winston = require('winston');
var rimraf = require('rimraf');
var mkdirp = require('mkdirp');
var async = require('async');
 
var plugins = require('../plugins');
var db = require('../database');
 
module.exports = function (Meta) {
 
	Meta.sounds = {};
 
	Meta.sounds.init = function (callback) {
		if (nconf.get('isPrimary') === 'true') {
			setupSounds(callback);
		} else {
			if (typeof callback === 'function') {
				callback();
			}
		}
	};
 
	Meta.sounds.getFiles = function (callback) {
		async.waterfall([
			function (next) {
				fs.readdir(path.join(__dirname, '../../public/sounds'), next);
			},
			function (sounds, next) {
				fs.readdir(path.join(__dirname, '../../public/uploads/sounds'), function (err, uploaded) {
					next(err, sounds.concat(uploaded));
				});
			}
		], function (err, files) {
			var	localList = {};
 
			// Filter out hidden files
			files = files.filter(function (filename) {
				return !filename.startsWith('.');
			});
 
			if (err) {
				winston.error('Could not get local sound files:' + err.message);
				console.log(err.stack);
				return callback(null, []);
			}
 
			// Return proper paths
			files.forEach(function (filename) {
				localList[filename] = nconf.get('relative_path') + '/sounds/' + filename;
			});
 
			callback(null, localList);
		});
	};
 
	Meta.sounds.getMapping = function (uid, callback) {
		var user = require('../user');
		async.parallel({
			defaultMapping: function (next) {
				db.getObject('settings:sounds', next);
			},
			userSettings: function (next) {
				user.getSettings(uid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var userSettings = results.userSettings;
			var defaultMapping = results.defaultMapping || {};
			var soundMapping = {};
			soundMapping.notification = (userSettings.notificationSound || userSettings.notificationSound === '') ?
				userSettings.notificationSound : defaultMapping.notification || '';
 
			soundMapping['chat-incoming'] = (userSettings.incomingChatSound || userSettings.incomingChatSound === '') ?
				userSettings.incomingChatSound : defaultMapping['chat-incoming'] || '';
 
			soundMapping['chat-outgoing'] = (userSettings.outgoingChatSound || userSettings.outgoingChatSound === '') ?
				userSettings.outgoingChatSound : defaultMapping['chat-outgoing'] || '';
 
			callback(null, soundMapping);
		});
	};
 
	function setupSounds(callback) {
		var	soundsPath = path.join(__dirname, '../../public/sounds');
 
		async.waterfall([
			function (next) {
				fs.readdir(path.join(__dirname, '../../public/uploads/sounds'), next);
			},
			function (uploaded, next) {
				uploaded = uploaded.filter(function (filename) {
					return !filename.startsWith('.');
				}).map(function (filename) {
					return path.join(__dirname, '../../public/uploads/sounds', filename);
				});
 
				plugins.fireHook('filter:sounds.get', uploaded, function (err, filePaths) {
					if (err) {
						winston.error('Could not initialise sound files:' + err.message);
						return;
					}
 
					if (nconf.get('local-assets') === false) {
						// Don't regenerate the public/sounds/ directory. Instead, create a mapping for the router to use
						Meta.sounds._filePathHash = filePaths.reduce(function (hash, filePath) {
							hash[path.basename(filePath)] = filePath;
							return hash;
						}, {});
 
						winston.verbose('[sounds] Sounds OK');
						if (typeof next === 'function') {
							return next();
						} else {
							return;
						}
					}
 
					// Clear the sounds directory
					async.series([
						function (next) {
							rimraf(soundsPath, next);
						},
						function (next) {
							mkdirp(soundsPath, next);
						}
					], function (err) {
						if (err) {
							winston.error('Could not initialise sound files:' + err.message);
							return;
						}
 
						// Link paths
						async.each(filePaths, function (filePath, next) {
							if (process.platform === 'win32') {
								fs.link(filePath, path.join(soundsPath, path.basename(filePath)), next);
							} else {
								fs.symlink(filePath, path.join(soundsPath, path.basename(filePath)), 'file', next);
							}
						}, function (err) {
							if (!err) {
								winston.verbose('[sounds] Sounds OK');
							} else {
								winston.error('[sounds] Could not initialise sounds: ' + err.message);
							}
 
							if (typeof next === 'function') {
								next();
							}
						});
					});
				});
			}
		], callback);
	}
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/tags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/tags.js

Statements: 17.65% (6 / 34)      Branches: 0% (0 / 28)      Functions: 0% (0 / 8)      Lines: 17.65% (6 / 34)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134    2 2 2 2 2                                                                                                                                                                                                                           1                                  
'use strict';
 
var nconf = require('nconf');
var validator = require('validator');
var async = require('async');
var winston = require('winston');
var plugins = require('../plugins');
 
module.exports = function (Meta) {
	Meta.tags = {};
 
	Meta.tags.parse = function (meta, link, callback) {
		async.parallel({
			tags: function (next) {
				var defaultTags = [{
					name: 'viewport',
					content: 'width=device-width, initial-scale=1.0'
				}, {
					name: 'content-type',
					content: 'text/html; charset=UTF-8',
					noEscape: true
				}, {
					name: 'apple-mobile-web-app-capable',
					content: 'yes'
				}, {
					name: 'mobile-web-app-capable',
					content: 'yes'
				}, {
					property: 'og:site_name',
					content: Meta.config.title || 'NodeBB'
				}, {
					name: 'keywords',
					content: Meta.config.keywords || ''
				}, {
					name: 'msapplication-badge',
					content: 'frequency=30; polling-uri=' + nconf.get('url') + '/sitemap.xml',
					noEscape: true
				}, {
					name: 'msapplication-square150x150logo',
					content: Meta.config['brand:logo'] || '',
					noEscape: true
				}];
				plugins.fireHook('filter:meta.getMetaTags', defaultTags, next);
			},
			links: function (next) {
				var defaultLinks = [{
					rel: "icon",
					type: "image/x-icon",
					href: nconf.get('relative_path') + '/favicon.ico' + (Meta.config['cache-buster'] ? '?' + Meta.config['cache-buster'] : '')
				}, {
					rel: "manifest",
					href: nconf.get('relative_path') + '/manifest.json'
				}];
 
				// Touch icons for mobile-devices
				if (Meta.config['brand:touchIcon']) {
					defaultLinks.push({
						rel: 'apple-touch-icon',
						href: nconf.get('relative_path') + '/apple-touch-icon'
					}, {
						rel: 'icon',
						sizes: '36x36',
						href: nconf.get('relative_path') + '/uploads/system/touchicon-36.png'
					}, {
						rel: 'icon',
						sizes: '48x48',
						href: nconf.get('relative_path') + '/uploads/system/touchicon-48.png'
					}, {
						rel: 'icon',
						sizes: '72x72',
						href: nconf.get('relative_path') + '/uploads/system/touchicon-72.png'
					}, {
						rel: 'icon',
						sizes: '96x96',
						href: nconf.get('relative_path') + '/uploads/system/touchicon-96.png'
					}, {
						rel: 'icon',
						sizes: '144x144',
						href: nconf.get('relative_path') + '/uploads/system/touchicon-144.png'
					}, {
						rel: 'icon',
						sizes: '192x192',
						href: nconf.get('relative_path') + '/uploads/system/touchicon-192.png'
					});
				}
				plugins.fireHook('filter:meta.getLinkTags', defaultLinks, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			meta = results.tags.concat(meta || []).map(function (tag) {
				if (!tag || typeof tag.content !== 'string') {
					winston.warn('Invalid meta tag. ', tag);
					return tag;
				}
 
				if (!tag.noEscape) {
					tag.content = validator.escape(String(tag.content));
				}
 
				return tag;
			});
 
			addDescription(meta);
 
			link = results.links.concat(link || []);
 
			callback(null, {
				meta: meta,
				link: link
			});
		});
	};
 
	function addDescription(meta) {
		var hasDescription = false;
		meta.forEach(function (tag) {
			if (tag.name === 'description') {
				hasDescription = true;
			}
		});
 
		if (!hasDescription) {
			meta.push({
				name: 'description',
				content: validator.escape(String(Meta.config.description || ''))
			});
		}
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/templates.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/templates.js

Statements: 16.25% (13 / 80)      Branches: 0% (0 / 22)      Functions: 0% (0 / 25)      Lines: 16.25% (13 / 80)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168    2 2 2 2 2 2 2   2                         1                             1                                                                                                                                       1                                                                                                       1       1          
"use strict";
 
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var winston = require('winston');
var async = require('async');
var path = require('path');
var fs = require('fs');
var nconf = require('nconf');
 
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
 
var Templates = {};
var searchIndex = {};
 
Templates.compile = function (callback) {
	callback = callback || function () {};
 
	compile(callback);
};
 
 
function getBaseTemplates(theme) {
	var baseTemplatesPaths = [],
		baseThemePath, baseThemeConfig;
 
	while (theme) {
		baseThemePath = path.join(nconf.get('themes_path'), theme);
		baseThemeConfig = require(path.join(baseThemePath, 'theme.json'));
 
		baseTemplatesPaths.push(path.join(baseThemePath, baseThemeConfig.templates || 'templates'));
		theme = baseThemeConfig.baseTheme;
	}
 
	return baseTemplatesPaths.reverse();
}
 
function preparePaths(baseTemplatesPaths, callback) {
	var coreTemplatesPath = nconf.get('core_templates_path');
	var viewsPath = nconf.get('views_dir');
 
	async.waterfall([
		function (next) {
			rimraf(viewsPath, next);
		},
		function (next) {
			mkdirp(viewsPath, next);
		},
		function (viewsPath, next) {
			plugins.fireHook('static:templates.precompile', {}, next);
		},
		function (next) {
			plugins.getTemplates(next);
		}
	], function (err, pluginTemplates) {
		if (err) {
			return callback(err);
		}
 
		winston.verbose('[meta/templates] Compiling templates');
 
		async.parallel({
			coreTpls: function (next) {
				utils.walk(coreTemplatesPath, next);
			},
			baseThemes: function (next) {
				async.map(baseTemplatesPaths, function (baseTemplatePath, next) {
					utils.walk(baseTemplatePath, function (err, paths) {
						paths = paths.map(function (tpl) {
							return {
								base: baseTemplatePath,
								path: tpl.replace(baseTemplatePath, '')
							};
						});
 
						next(err, paths);
					});
				}, next);
			}
		}, function (err, data) {
			var baseThemes = data.baseThemes,
				coreTpls = data.coreTpls,
				paths = {};
 
			coreTpls.forEach(function (el, i) {
				paths[coreTpls[i].replace(coreTemplatesPath, '')] = coreTpls[i];
			});
 
			baseThemes.forEach(function (baseTpls) {
				baseTpls.forEach(function (el, i) {
					paths[baseTpls[i].path] = path.join(baseTpls[i].base, baseTpls[i].path);
				});
			});
 
			for (var tpl in pluginTemplates) {
				if (pluginTemplates.hasOwnProperty(tpl)) {
					paths[tpl] = pluginTemplates[tpl];
				}
			}
 
			callback(err, paths);
		});
	});
}
 
function compile(callback) {
	var themeConfig = require(nconf.get('theme_config')),
		baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')],
		viewsPath = nconf.get('views_dir');
 
 
	preparePaths(baseTemplatesPaths, function (err, paths) {
		if (err) {
			return callback(err);
		}
 
		async.each(Object.keys(paths), function (relativePath, next) {
			var file = fs.readFileSync(paths[relativePath]).toString(),
				matches = null,
				regex = /[ \t]*<!-- IMPORT ([\s\S]*?)? -->[ \t]*/;
 
			while((matches = file.match(regex)) !== null) {
				var partial = "/" + matches[1];
 
				if (paths[partial] && relativePath !== partial) {
					file = file.replace(regex, fs.readFileSync(paths[partial]).toString());
				} else {
					winston.warn('[meta/templates] Partial not loaded: ' + matches[1]);
					file = file.replace(regex, "");
				}
			}
 
			if (relativePath.match(/^\/admin\/[\s\S]*?/)) {
				addIndex(relativePath, file);
			}
 
			mkdirp.sync(path.join(viewsPath, relativePath.split('/').slice(0, -1).join('/')));
			fs.writeFile(path.join(viewsPath, relativePath), file, next);
		}, function (err) {
			if (err) {
				winston.error('[meta/templates] ' + err.stack);
				return callback(err);
			}
 
			compileIndex(viewsPath, function (err) {
				if (err) {
					return callback(err);
				}
				winston.verbose('[meta/templates] Successfully compiled templates.');
 
				callback();
			});
		});
	});
}
 
 
function addIndex(path, file) {
	searchIndex[path] = file;
}
 
function compileIndex(viewsPath, callback) {
	fs.writeFile(path.join(viewsPath, '/indexed.json'), JSON.stringify(searchIndex), callback);
}
 
module.exports = Templates;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/themes.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/meta/themes.js

Statements: 8.64% (7 / 81)      Branches: 0% (0 / 34)      Functions: 0% (0 / 20)      Lines: 8.64% (7 / 81)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169      2 2 2 2 2   2 2                                                                                                                                                                                                                                                                                                                            
 
'use strict';
 
var nconf = require('nconf');
var winston = require('winston');
var fs = require('fs');
var path = require('path');
var async = require('async');
 
var file = require('../file');
var db = require('../database');
 
module.exports = function (Meta) {
	Meta.themes = {};
 
	Meta.themes.get = function (callback) {
		var themePath = nconf.get('themes_path');
		if (typeof themePath !== 'string') {
			return callback(null, []);
		}
 
		fs.readdir(themePath, function (err, files) {
			if (err) {
				return callback(err);
			}
 
			async.filter(files, function (file, next) {
				fs.stat(path.join(themePath, file), function (err, fileStat) {
					if (err) {
						return next(false);
					}
 
					next((fileStat.isDirectory() && file.slice(0, 13) === 'nodebb-theme-'));
				});
			}, function (themes) {
				async.map(themes, function (theme, next) {
					var config = path.join(themePath, theme, 'theme.json');
 
					fs.readFile(config, function (err, file) {
						if (err) {
							return next();
						}
						try {
							var configObj = JSON.parse(file.toString());
 
							// Minor adjustments for API output
							configObj.type = 'local';
							if (configObj.screenshot) {
								configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id;
							} else {
								configObj.screenshot_url = nconf.get('relative_path') + '/images/themes/default.png';
							}
							next(null, configObj);
						} catch (err) {
							winston.error('[themes] Unable to parse theme.json ' + theme);
							next(null, null);
						}
					});
 
				}, function (err, themes) {
					if (err) {
						return callback(err);
					}
 
					themes = themes.filter(Boolean);
					callback(null, themes);
				});
			});
		});
	};
 
	Meta.themes.set = function (data, callback) {
		var	themeData = {
			'theme:type': data.type,
			'theme:id': data.id,
			'theme:staticDir': '',
			'theme:templates': '',
			'theme:src': ''
		};
 
		switch(data.type) {
		case 'local':
			async.waterfall([
				async.apply(Meta.configs.get, 'theme:id'),
				function (current, next) {
					async.series([
						async.apply(db.sortedSetRemove, 'plugins:active', current),
						async.apply(db.sortedSetAdd, 'plugins:active', 0, data.id)
					], function (err) {
						next(err);
					});
				},
				function (next) {
					fs.readFile(path.join(nconf.get('themes_path'), data.id, 'theme.json'), function (err, config) {
						if (!err) {
							config = JSON.parse(config.toString());
							next(null, config);
						} else {
							next(err);
						}
					});
				},
				function (config, next) {
					themeData['theme:staticDir'] = config.staticDir ? config.staticDir : '';
					themeData['theme:templates'] = config.templates ? config.templates : '';
					themeData['theme:src'] = '';
 
					db.setObject('config', themeData, next);
 
					// Re-set the themes path (for when NodeBB is reloaded)
					Meta.themes.setPath(config);
				}
			], callback);
 
			Meta.reloadRequired = true;
			break;
 
		case 'bootswatch':
			Meta.configs.set('theme:src', data.src, callback);
			break;
		}
	};
 
	Meta.themes.setupPaths = function (callback) {
		async.parallel({
			themesData: Meta.themes.get,
			currentThemeId: function (next) {
				db.getObjectField('config', 'theme:id', next);
			}
		}, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			var themeId = data.currentThemeId || 'nodebb-theme-persona';
 
			var	themeObj = data.themesData.filter(function (themeObj) {
					return themeObj.id === themeId;
				})[0];
 
			if (process.env.NODE_ENV === 'development') {
				winston.info('[themes] Using theme ' + themeId);
			}
 
			if (!themeObj) {
				return callback(new Error('[[error:theme-not-found]]'));
			}
 
			Meta.themes.setPath(themeObj);
			callback();
		});
	};
 
	Meta.themes.setPath = function (themeObj) {
		// Theme's templates path
		var themePath = nconf.get('base_templates_path'),
			fallback = path.join(nconf.get('themes_path'), themeObj.id, 'templates');
 
		if (themeObj.templates) {
			themePath = path.join(nconf.get('themes_path'), themeObj.id, themeObj.templates);
		} else if (file.existsSync(fallback)) {
			themePath = fallback;
		}
 
		nconf.set('theme_templates_path', themePath);
		nconf.set('theme_config', path.join(nconf.get('themes_path'), themeObj.id, 'theme.json'));
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/

Statements: 7.76% (35 / 451)      Branches: 0% (0 / 266)      Functions: 0% (0 / 101)      Lines: 7.76% (35 / 451)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/middleware/
File Statements Branches Functions Lines
admin.js 5.56% (3 / 54) 0% (0 / 20) 0% (0 / 16) 5.56% (3 / 54)
header.js 4.49% (4 / 89) 0% (0 / 63) 0% (0 / 21) 4.49% (4 / 89)
headers.js 6.67% (1 / 15) 0% (0 / 16) 0% (0 / 3) 6.67% (1 / 15)
index.js 8.74% (9 / 103) 0% (0 / 58) 0% (0 / 20) 8.74% (9 / 103)
maintenance.js 7.69% (2 / 26) 0% (0 / 14) 0% (0 / 5) 7.69% (2 / 26)
ratelimit.js 30% (6 / 20) 0% (0 / 12) 0% (0 / 1) 30% (6 / 20)
render.js 11.11% (7 / 63) 0% (0 / 31) 0% (0 / 16) 11.11% (7 / 63)
user.js 3.7% (3 / 81) 0% (0 / 52) 0% (0 / 19) 3.7% (3 / 81)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/admin.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/admin.js

Statements: 5.56% (3 / 54)      Branches: 0% (0 / 20)      Functions: 0% (0 / 16)      Lines: 5.56% (3 / 54)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124    2 2 2                                                                                                                                                                                                                                              
"use strict";
 
var async = require('async');
var winston = require('winston');
var user = require('../user');
var meta = require('../meta');
var plugins = require('../plugins');
 
var controllers = {
	api: require('../controllers/api'),
	helpers: require('../controllers/helpers')
};
 
module.exports = function (middleware) {
	middleware.admin = {};
	middleware.admin.isAdmin = function (req, res, next) {
		winston.warn('[middleware.admin.isAdmin] deprecation warning, no need to use this from plugins!');
 
		if (!req.user) {
			return controllers.helpers.notAllowed(req, res);
		}
 
		user.isAdministrator(req.user.uid, function (err, isAdmin) {
			if (err || isAdmin) {
				return next(err);
			}
 
			controllers.helpers.notAllowed(req, res);
		});
	};
 
	middleware.admin.buildHeader = function (req, res, next) {
		res.locals.renderAdminHeader = true;
 
		controllers.api.getConfig(req, res, function (err, config) {
			if (err) {
				return next(err);
			}
 
			res.locals.config = config;
			next();
		});
	};
 
	middleware.admin.renderHeader = function (req, res, data, next) {
		var custom_header = {
			'plugins': [],
			'authentication': []
		};
 
		user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed'], function (err, userData) {
			if (err) {
				return next(err);
			}
 
			userData.uid = req.uid;
			userData['email:confirmed'] = parseInt(userData['email:confirmed'], 10) === 1;
 
			async.parallel({
				scripts: function (next) {
					plugins.fireHook('filter:admin.scripts.get', [], function (err, scripts) {
						if (err) {
							return next(err);
						}
						var arr = [];
						scripts.forEach(function (script) {
							arr.push({src: script});
						});
 
						next(null, arr);
					});
				},
				custom_header: function (next) {
					plugins.fireHook('filter:admin.header.build', custom_header, next);
				},
				config: function (next) {
					controllers.api.getConfig(req, res, next);
				},
				configs: function (next) {
					meta.configs.list(next);
				}
			}, function (err, results) {
				if (err) {
					return next(err);
				}
				res.locals.config = results.config;
 
				var acpPath = req.path.slice(1).split('/');
				acpPath.forEach(function (path, i) {
					acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1);
				});
				acpPath = acpPath.join(' > ');
 
				var templateValues = {
					config: results.config,
					configJSON: JSON.stringify(results.config),
					relative_path: results.config.relative_path,
					adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)),
					user: userData,
					userJSON: JSON.stringify(userData).replace(/'/g, "\\'"),
					plugins: results.custom_header.plugins,
					authentication: results.custom_header.authentication,
					scripts: results.scripts,
					'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
					env: process.env.NODE_ENV ? true : false,
					title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel',
					bodyClass: data.bodyClass
				};
 
				templateValues.template = {name: res.locals.template};
				templateValues.template[res.locals.template] = true;
 
				req.app.render('admin/header', templateValues, next);
			});
		});
	};
 
 
	middleware.admin.renderFooter = function (req, res, data, next) {
		req.app.render('admin/footer', data, next);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/header.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/header.js

Statements: 4.49% (4 / 89)      Branches: 0% (0 / 63)      Functions: 0% (0 / 21)      Lines: 4.49% (4 / 89)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193    2 2   2                                                                                                                                                                                                                                                                                                                                             1                                        
'use strict';
 
var async = require('async');
var nconf = require('nconf');
 
var db = require('../database');
var user = require('../user');
var meta = require('../meta');
var plugins = require('../plugins');
var navigation = require('../navigation');
 
var controllers = {
	api: require('../controllers/api'),
	helpers: require('../controllers/helpers')
};
 
module.exports = function (middleware) {
 
	middleware.buildHeader = function (req, res, next) {
		res.locals.renderHeader = true;
		res.locals.isAPI = false;
		async.waterfall([
			function (next) {
				middleware.applyCSRF(req, res, next);
			},
			function (next) {
				async.parallel({
					config: function (next) {
						controllers.api.getConfig(req, res, next);
					},
					plugins: function (next) {
						plugins.fireHook('filter:middleware.buildHeader', {req: req, locals: res.locals}, next);
					}
				}, next);
			},
			function (results, next) {
				res.locals.config = results.config;
				next();
			}
		], next);
	};
 
	middleware.renderHeader = function (req, res, data, callback) {
		var registrationType = meta.config.registrationType || 'normal';
		var templateValues = {
			bootswatchCSS: meta.config['theme:src'],
			title: meta.config.title || '',
			description: meta.config.description || '',
			'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
			'brand:logo': meta.config['brand:logo'] || '',
			'brand:logo:url': meta.config['brand:logo:url'] || '',
			'brand:logo:alt': meta.config['brand:logo:alt'] || '',
			'brand:logo:display': meta.config['brand:logo'] ? '' : 'hide',
			allowRegistration: registrationType === 'normal' || registrationType === 'admin-approval' || registrationType === 'admin-approval-ip',
			searchEnabled: plugins.hasListeners('filter:search.query'),
			config: res.locals.config,
			relative_path: nconf.get('relative_path'),
			bodyClass: data.bodyClass
		};
 
		templateValues.configJSON = JSON.stringify(res.locals.config);
 
		async.parallel({
			scripts: function (next) {
				plugins.fireHook('filter:scripts.get', [], next);
			},
			isAdmin: function (next) {
				user.isAdministrator(req.uid, next);
			},
			isGlobalMod: function (next) {
				user.isGlobalModerator(req.uid, next);
			},
			isModerator: function (next) {
				user.isModeratorOfAnyCategory(req.uid, next);
			},
			user: function (next) {
				var userData = {
					uid: 0,
					username: '[[global:guest]]',
					userslug: '',
					email: '',
					picture: meta.config.defaultAvatar,
					status: 'offline',
					reputation: 0,
					'email:confirmed': false
				};
				if (req.uid) {
					user.getUserFields(req.uid, Object.keys(userData), next);
				} else {
					next(null, userData);
				}
			},
			isEmailConfirmSent: function (next) {
				if (!meta.config.requireEmailConfirmation || !req.uid) {
					return next(null, false);
				}
				db.get('uid:' + req.uid + ':confirm:email:sent', next);
			},
			navigation: async.apply(navigation.get),
			tags: async.apply(meta.tags.parse, res.locals.metaTags, res.locals.linkTags),
			banned: async.apply(user.isBanned, req.uid),
			banReason: async.apply(user.getBannedReason, req.uid)
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (results.banned) {
				req.logout();
				return res.redirect('/?banned=' + (results.banReason || 'no-reason'));
			}
 
			results.user.isAdmin = results.isAdmin;
			results.user.isGlobalMod = results.isGlobalMod;
			results.user.isMod = !!results.isModerator;
			results.user.uid = parseInt(results.user.uid, 10);
			results.user.email = String(results.user.email).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
			results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
			results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;
 
			if (res.locals.config && parseInt(meta.config.disableCustomUserSkins, 10) !== 1 && res.locals.config.bootswatchSkin !== 'default') {
				templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css';
			}
 
			templateValues.browserTitle = controllers.helpers.buildTitle(data.title);
			templateValues.navigation = results.navigation;
			templateValues.metaTags = results.tags.meta;
			templateValues.linkTags = results.tags.link;
			templateValues.isAdmin = results.user.isAdmin;
			templateValues.isGlobalMod = results.user.isGlobalMod;
			templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod;
			templateValues.user = results.user;
			templateValues.userJSON = JSON.stringify(results.user);
			templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1 && meta.config.customCSS;
			templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : '';
			templateValues.useCustomJS = parseInt(meta.config.useCustomJS, 10) === 1;
			templateValues.customJS = templateValues.useCustomJS ? meta.config.customJS : '';
			templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin;
			templateValues.defaultLang = meta.config.defaultLang || 'en-GB';
			templateValues.privateUserInfo = parseInt(meta.config.privateUserInfo, 10) === 1;
			templateValues.privateTagListing = parseInt(meta.config.privateTagListing, 10) === 1;
 
			templateValues.template = {name: res.locals.template};
			templateValues.template[res.locals.template] = true;
 
			templateValues.scripts = results.scripts.map(function (script) {
				return {src: script};
			});
 
			if (req.route && req.route.path === '/') {
				modifyTitle(templateValues);
			}
 
			plugins.fireHook('filter:middleware.renderHeader', {templateValues: templateValues, req: req, res: res}, function (err, data) {
				if (err) {
					return callback(err);
				}
 
				req.app.render('header', data.templateValues, callback);
			});
		});
	};
 
	middleware.renderFooter = function (req, res, data, callback) {
		plugins.fireHook('filter:middleware.renderFooter', {templateValues: data, req: req, res: res}, function (err, data) {
			if (err) {
				return callback(err);
			}
			req.app.render('footer', data.templateValues, callback);
		});
	};
 
	function modifyTitle(obj) {
		var title = controllers.helpers.buildTitle('[[pages:home]]');
		obj.browserTitle = title;
 
		if (obj.metaTags) {
			obj.metaTags.forEach(function (tag, i) {
				if (tag.property === 'og:title') {
					obj.metaTags[i].content = title;
				}
			});
		}
 
		return title;
	}
 
};
 
 
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/headers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/headers.js

Statements: 6.67% (1 / 15)      Branches: 0% (0 / 16)      Functions: 0% (0 / 3)      Lines: 6.67% (1 / 15)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42    2                                                                              
'use strict';
 
var meta = require('../meta');
 
module.exports = function (middleware) {
 
	middleware.addHeaders = function (req, res, next) {
		var headers = {
			'X-Powered-By': encodeURI(meta.config['powered-by'] || 'NodeBB'),
			'X-Frame-Options': meta.config['allow-from-uri'] ? 'ALLOW-FROM ' + encodeURI(meta.config['allow-from-uri']) : 'SAMEORIGIN',
			'Access-Control-Allow-Origin': encodeURI(meta.config['access-control-allow-origin'] || 'null'),
			'Access-Control-Allow-Methods': encodeURI(meta.config['access-control-allow-methods'] || ''),
			'Access-Control-Allow-Headers': encodeURI(meta.config['access-control-allow-headers'] || '')
		};
 
		for (var key in headers) {
			if (headers.hasOwnProperty(key) && headers[key]) {
				res.setHeader(key, headers[key]);
			}
		}
 
		next();
	};
 
	middleware.addExpiresHeaders = function (req, res, next) {
		if (req.app.enabled('cache')) {
			res.setHeader("Cache-Control", "public, max-age=5184000");
			res.setHeader("Expires", new Date(Date.now() + 5184000000).toUTCString());
		} else {
			res.setHeader("Cache-Control", "public, max-age=0");
			res.setHeader("Expires", new Date().toUTCString());
		}
 
		next();
	};
 
};
 
 
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/index.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/index.js

Statements: 8.74% (9 / 103)      Branches: 0% (0 / 58)      Functions: 0% (0 / 20)      Lines: 8.74% (9 / 103)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204    2 2 2 2 2 2 2 2                                                                                                                                                                                                                                 1                                                                                                                                                                  
"use strict";
 
var async = require('async');
var fs = require('fs');
var path = require('path');
var csrf = require('csurf');
var validator = require('validator');
var nconf = require('nconf');
var ensureLoggedIn = require('connect-ensure-login');
var toobusy = require('toobusy-js');
 
var plugins = require('../plugins');
var languages = require('../languages');
var meta = require('../meta');
var user = require('../user');
var groups = require('../groups');
 
var analytics = require('../analytics');
 
var controllers = {
	api: require('./../controllers/api'),
	helpers: require('../controllers/helpers')
};
 
var middleware = {};
 
middleware.applyCSRF = csrf();
 
middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login');
 
require('./admin')(middleware);
require('./header')(middleware);
require('./render')(middleware);
require('./maintenance')(middleware);
require('./user')(middleware);
require('./headers')(middleware);
 
middleware.authenticate = function (req, res, next) {
	if (req.user) {
		return next();
	} else if (plugins.hasListeners('action:middleware.authenticate')) {
		return plugins.fireHook('action:middleware.authenticate', {
			req: req,
			res: res,
			next: next
		});
	}
 
	controllers.helpers.notAllowed(req, res);
};
 
middleware.pageView = function (req, res, next) {
	analytics.pageView({
		ip: req.ip,
		path: req.path,
		uid: req.uid
	});
 
	plugins.fireHook('action:middleware.pageView', {req: req});
 
	if (req.user) {
		user.updateLastOnlineTime(req.user.uid);
		if (req.path.startsWith('/api/users') || req.path.startsWith('/users')) {
			user.updateOnlineUsers(req.user.uid, next);
		} else {
			user.updateOnlineUsers(req.user.uid);
			next();
		}
	} else {
		next();
	}
};
 
 
middleware.pluginHooks = function (req, res, next) {
	async.each(plugins.loadedHooks['filter:router.page'] || [], function (hookObj, next) {
		hookObj.method(req, res, next);
	}, function () {
		// If it got here, then none of the subscribed hooks did anything, or there were no hooks
		next();
	});
};
 
middleware.validateFiles = function (req, res, next) {
	if (!Array.isArray(req.files.files) || !req.files.files.length) {
		return next(new Error(['[[error:invalid-files]]']));
	}
 
	next();
};
 
middleware.prepareAPI = function (req, res, next) {
	res.locals.isAPI = true;
	next();
};
 
middleware.routeTouchIcon = function (req, res) {
	if (meta.config['brand:touchIcon'] && validator.isURL(meta.config['brand:touchIcon'])) {
		return res.redirect(meta.config['brand:touchIcon']);
	} else {
		return res.sendFile(path.join(__dirname, '../../public', meta.config['brand:touchIcon'] || '/logo.png'), {
			maxAge: req.app.enabled('cache') ? 5184000000 : 0
		});
	}
};
 
middleware.privateTagListing = function (req, res, next) {
	if (!req.user && parseInt(meta.config.privateTagListing, 10) === 1) {
		controllers.helpers.notAllowed(req, res);
	} else {
		next();
	}
};
 
middleware.exposeGroupName = function (req, res, next) {
	expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next);
};
 
middleware.exposeUid = function (req, res, next) {
	expose('uid', user.getUidByUserslug, 'userslug', req, res, next);
};
 
function expose(exposedField, method, field, req, res, next) {
	if (!req.params.hasOwnProperty(field)) {
		return next();
	}
	method(req.params[field], function (err, id) {
		if (err) {
			return next(err);
		}
 
		res.locals[exposedField] = id;
		next();
	});
}
 
middleware.privateUploads = function (req, res, next) {
	if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) {
		return next();
	}
	if (req.path.startsWith('/uploads/files')) {
		return res.status(403).json('not-allowed');
	}
	next();
};
 
middleware.busyCheck = function (req, res, next) {
	if (global.env === 'production' && (!meta.config.hasOwnProperty('eventLoopCheckEnabled') || parseInt(meta.config.eventLoopCheckEnabled, 10) === 1) && toobusy()) {
		analytics.increment('errors:503');
		res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html'));
	} else {
		next();
	}
};
 
middleware.applyBlacklist = function (req, res, next) {
	meta.blacklist.test(req.ip, function (err) {
		next(err);
	});
};
 
middleware.getTranslation = function (req, res, next) {
	var language = req.params.language;
	var namespace = req.params.namespace;
 
	if (language && namespace) {
		languages.get(language, namespace, function (err, translations) {
			if (err) {
				return next(err);
			}
 
			res.status(200).json(translations);
		});
	} else {
		res.status(404).json('{}');
	}
};
 
middleware.processTimeagoLocales = function (req, res, next) {
	var fallback = req.path.indexOf('-short') === -1 ? 'jquery.timeago.en.js' : 'jquery.timeago.en-short.js',
		localPath = path.join(__dirname, '../../public/vendor/jquery/timeago/locales', req.path),
		exists;
 
	try {
		exists = fs.accessSync(localPath, fs.F_OK | fs.R_OK);
	} catch(e) {
		exists = false;
	}
 
	if (exists) {
		res.status(200).sendFile(localPath, {
			maxAge: req.app.enabled('cache') ? 5184000000 : 0
		});
	} else {
		res.status(200).sendFile(path.join(__dirname, '../../public/vendor/jquery/timeago/locales', fallback), {
			maxAge: req.app.enabled('cache') ? 5184000000 : 0
		});
	}
};
 
 
module.exports = middleware;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/maintenance.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/maintenance.js

Statements: 7.69% (2 / 26)      Branches: 0% (0 / 14)      Functions: 0% (0 / 5)      Lines: 7.69% (2 / 26)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67    2 2                                                                                                                              
'use strict';
 
var nconf = require('nconf');
var meta = require('../meta');
var user = require('../user');
 
module.exports = function (middleware) {
 
	middleware.maintenanceMode = function (req, res, next) {
		if (parseInt(meta.config.maintenanceMode, 10) !== 1) {
			return next();
		}
		var url = req.url.replace(nconf.get('relative_path'), '');
 
		var allowedRoutes = [
			'^/ping',
			'^/sping',
			'^/login',
			'^/stylesheet.css',
			'^/favicon',
			'^/nodebb.min.js',
			'^/vendor/fontawesome/fonts/fontawesome-webfont.woff',
			'^/src/(modules|client)/[\\w/]+.js',
			'^/templates/[\\w/]+.tpl',
			'^/api/login',
			'^/api/widgets/render',
			'^/api/language/.+',
			'^/uploads/system/site-logo.png'
		];
 
		var isAllowed = function (url) {
			for(var x = 0,numAllowed = allowedRoutes.length,route; x < numAllowed; x++) {
				route = new RegExp(allowedRoutes[x]);
				if (route.test(url)) {
					return true;
				}
			}
			return false;
		};
 
		if (isAllowed(url)) {
			return next();
		}
 
		user.isAdministrator(req.uid, function (err, isAdmin) {
			if (err || isAdmin) {
				return next(err);
			}
 
			res.status(503);
			var data = {
				site_title: meta.config.title || 'NodeBB',
				message: meta.config.maintenanceModeMessage
			};
 
			if (res.locals.isAPI) {
				return res.json(data);
			}
 
			middleware.buildHeader(req, res, function () {
				res.render('503', data);
			});
		});
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/ratelimit.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/ratelimit.js

Statements: 30% (6 / 20)      Branches: 0% (0 / 12)      Functions: 0% (0 / 1)      Lines: 30% (6 / 20)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37      1   1   1 1   1                                               1    
 
 
'use strict';
var winston = require('winston');
 
var ratelimit = {};
 
var allowedCalls = 100;
var timeframe = 10000;
 
ratelimit.isFlooding = function (socket) {
	socket.callsPerSecond = socket.callsPerSecond || 0;
	socket.elapsedTime = socket.elapsedTime || 0;
	socket.lastCallTime = socket.lastCallTime || Date.now();
 
	++socket.callsPerSecond;
 
	var now = Date.now();
	socket.elapsedTime += now - socket.lastCallTime;
 
	if (socket.callsPerSecond > allowedCalls && socket.elapsedTime < timeframe) {
		winston.warn('Flooding detected! Calls : ' + socket.callsPerSecond + ', Duration : ' + socket.elapsedTime);
		return true;
	}
 
	if (socket.elapsedTime >= timeframe) {
		socket.elapsedTime = 0;
		socket.callsPerSecond = 0;
	}
 
	socket.lastCallTime = now;
	return false;
};
 
module.exports = ratelimit;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/render.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/render.js

Statements: 11.11% (7 / 63)      Branches: 0% (0 / 31)      Functions: 0% (0 / 16)      Lines: 11.11% (7 / 63)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121    2 2 2   2                                                                                                                                                                         1                   1               1                      
'use strict';
 
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
 
var plugins = require('../plugins');
var translator = require('../../public/src/modules/translator');
 
module.exports = function (middleware) {
 
	middleware.processRender = function (req, res, next) {
		// res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687
		var render = res.render;
		res.render = function (template, options, fn) {
			var self = this;
			var req = this.req;
			var defaultFn = function (err, str) {
				if (err) {
					return next(err);
				}
				self.send(str);
			};
 
			options = options || {};
			if ('function' === typeof options) {
				fn = options;
				options = {};
			}
			if ('function' !== typeof fn) {
				fn = defaultFn;
			}
 
			var ajaxifyData;
			async.waterfall([
				function (next) {
					plugins.fireHook('filter:' + template + '.build', {req: req, res: res, templateData: options}, next);
				},
				function (data, next) {
					options = data.templateData;
 
					options.loggedIn = !!req.uid;
					options.relative_path = nconf.get('relative_path');
					options.template = {name: template};
					options.template[template] = true;
					options.url = (req.baseUrl + req.path).replace(/^\/api/, '');
					options.bodyClass = buildBodyClass(req);
 
					res.locals.template = template;
					options._locals = undefined;
 
					if (res.locals.isAPI) {
						if (req.route && req.route.path === '/api/') {
							options.title = '[[pages:home]]';
						}
 
						return res.json(options);
					}
 
					ajaxifyData = JSON.stringify(options).replace(/<\//g, '<\\/');
 
					async.parallel({
						header: function (next) {
							renderHeaderFooter('renderHeader', req, res, options, next);
						},
						content: function (next) {
							render.call(self, template, options, next);
						},
						footer: function (next) {
							renderHeaderFooter('renderFooter', req, res, options, next);
						}
					}, next);
				},
				function (results, next) {
					var str = results.header +
						(res.locals.postHeader || '') +
						results.content +
						(res.locals.preFooter || '') +
						results.footer;
 
					translate(str, req, res, next);
				},
				function (translated, next) {
					next(null, translated + '<script id="ajaxify-data" type="application/json">' + ajaxifyData + '</script>');
				}
			], fn);
		};
 
		next();
	};
 
	function renderHeaderFooter(method, req, res, options, next) {
		if (res.locals.renderHeader) {
			middleware[method](req, res, options, next);
		} else if (res.locals.renderAdminHeader) {
			middleware.admin[method](req, res, options, next);
		} else {
			next(null, '');
		}
	}
 
	function translate(str, req, res, next) {
		var language = res.locals.config && res.locals.config.userLang || 'en-GB';
		language = req.query.lang ? validator.escape(String(req.query.lang)) : language;
		translator.translate(str, language, function (translated) {
			next(null, translator.unescape(translated));
		});
	}
 
	function buildBodyClass(req) {
		var clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, '');
		var parts = clean.split('/').slice(0, 3);
		parts.forEach(function (p, index) {
			parts[index] = index ? parts[0] + '-' + p : 'page-' + (p || 'home');
		});
		return parts.join(' ');
	}
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/user.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/middleware/user.js

Statements: 3.7% (3 / 81)      Branches: 0% (0 / 52)      Functions: 0% (0 / 19)      Lines: 3.7% (3 / 81)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163    2 2   2                                                                                                                                                                                                                                                                                                                          
'use strict';
 
var async = require('async');
var nconf =  require('nconf');
 
var meta = require('../meta');
var user = require('../user');
var privileges = require('../privileges');
 
var controllers = {
	helpers: require('../controllers/helpers')
};
 
module.exports = function (middleware) {
 
	middleware.checkGlobalPrivacySettings = function (req, res, next) {
		if (!req.user && !!parseInt(meta.config.privateUserInfo, 10)) {
			return controllers.helpers.notAllowed(req, res);
		}
 
		next();
	};
 
	middleware.checkAccountPermissions = function (req, res, next) {
		// This middleware ensures that only the requested user and admins can pass
		async.waterfall([
			function (next) {
				middleware.authenticate(req, res, next);
			},
			function (next) {
				user.getUidByUserslug(req.params.userslug, next);
			},
			function (uid, next) {
				privileges.users.canEdit(req.uid, uid, next);
			},
			function (allowed, next) {
				if (allowed) {
					return next(null, allowed);
				}
 
				// For the account/info page only, allow plain moderators through
				if (/user\/.+\/info$/.test(req.path)) {
					user.isModeratorOfAnyCategory(req.uid, next);
				} else {
					next(null, false);
				}
			}
		], function (err, allowed) {
			if (err || allowed) {
				return next(err);
			}
			controllers.helpers.notAllowed(req, res);
		});
	};
 
	middleware.redirectToAccountIfLoggedIn = function (req, res, next) {
		if (req.session.forceLogin) {
			return next();
		}
 
		if (!req.user) {
			return next();
		}
		user.getUserField(req.user.uid, 'userslug', function (err, userslug) {
			if (err) {
				return next(err);
			}
			controllers.helpers.redirect(res, '/user/' + userslug);
		});
	};
 
	middleware.redirectUidToUserslug = function (req, res, next) {
		var uid = parseInt(req.params.uid, 10);
		if (!uid) {
			return next();
		}
		user.getUserField(uid, 'userslug', function (err, userslug) {
			if (err || !userslug) {
				return next(err);
			}
 
			var path = req.path.replace(/^\/api/, '')
					.replace('uid', 'user')
					.replace(uid, function () { return userslug; });
			controllers.helpers.redirect(res, path);
		});
	};
 
	middleware.isAdmin = function (req, res, next) {
		if (!req.uid) {
			return controllers.helpers.notAllowed(req, res);
		}
 
		user.isAdministrator(req.uid, function (err, isAdmin) {
			if (err) {
				return next(err);
			}
 
			if (isAdmin) {
				user.hasPassword(req.uid, function (err, hasPassword) {
					if (err) {
						return next(err);
					}
 
					if (!hasPassword) {
						return next();
					}
 
					var loginTime = req.session.meta ? req.session.meta.datetime : 0;
					if (loginTime && parseInt(loginTime, 10) > Date.now() - 3600000) {
						var timeLeft = parseInt(loginTime, 10) - (Date.now() - 3600000);
						if (timeLeft < 300000) {
							req.session.meta.datetime += 300000;
						}
 
						return next();
					}
 
					req.session.returnTo = req.path.replace(/^\/api/, '');
					req.session.forceLogin = 1;
					if (res.locals.isAPI) {
						res.status(401).json({});
					} else {
						res.redirect(nconf.get('relative_path') + '/login');
					}
				});
				return;
			}
 
			if (res.locals.isAPI) {
				return controllers.helpers.notAllowed(req, res);
			}
 
			middleware.buildHeader(req, res, function () {
				controllers.helpers.notAllowed(req, res);
			});
		});
	};
 
	middleware.requireUser = function (req, res, next) {
		if (req.user) {
			return next();
		}
 
		res.status(403).render('403', {title: '[[global:403.title]]'});
	};
 
	middleware.registrationComplete = function (req, res, next) {
		// If the user's session contains registration data, redirect the user to complete registration
		if (!req.session.hasOwnProperty('registration')) {
			return next();
		} else {
			if (!req.path.endsWith('/register/complete')) {
				controllers.helpers.redirect(res, '/register/complete');
			} else {
				return next();
			}
		}
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/navigation/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/navigation/

Statements: 8.47% (5 / 59)      Branches: 0% (0 / 16)      Functions: 0% (0 / 15)      Lines: 8.47% (5 / 59)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/navigation/
File Statements Branches Functions Lines
admin.js 7.89% (3 / 38) 0% (0 / 6) 0% (0 / 11) 7.89% (3 / 38)
index.js 9.52% (2 / 21) 0% (0 / 10) 0% (0 / 4) 9.52% (2 / 21)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/navigation/admin.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/navigation/admin.js

Statements: 7.89% (3 / 38)      Branches: 0% (0 / 6)      Functions: 0% (0 / 11)      Lines: 7.89% (3 / 38)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76        4 4                                                                                                                       1                    
"use strict";
 
 
 
var async = require('async');
var plugins = require('../plugins');
var db = require('../database');
var translator = require('../../public/src/modules/translator');
var pubsub = require('../pubsub');
 
var admin = {};
admin.cache = null;
 
pubsub.on('admin:navigation:save', function () {
	admin.cache = null;
});
 
admin.save = function (data, callback) {
	var order = Object.keys(data);
	var items = data.map(function (item, idx) {
		var data = {};
 
		for (var i in item) {
			if (item.hasOwnProperty(i)) {
				item[i] = typeof item[i] === 'string' ? translator.escape(item[i]) : item[i];
			}
		}
 
		data[idx] = item;
		return JSON.stringify(data);
	});
 
	admin.cache = null;
	pubsub.publish('admin:navigation:save');
	async.waterfall([
		function (next) {
			db.delete('navigation:enabled', next);
		},
		function (next) {
			db.sortedSetAdd('navigation:enabled', order, items, next);
		}
	], callback);
};
 
admin.getAdmin = function (callback) {
	async.parallel({
		enabled: admin.get,
		available: getAvailable
	}, callback);
};
 
admin.get = function (callback) {
	db.getSortedSetRange('navigation:enabled', 0, -1, function (err, data) {
		if (err) {
			return callback(err);
		}
 
		data = data.map(function (item, idx) {
			return JSON.parse(item)[idx];
		});
 
		callback(null, data);
	});
};
 
function getAvailable(callback) {
	var core = require('../../install/data/navigation.json').map(function (item) {
		item.core = true;
		return item;
	});
 
	plugins.fireHook('filter:navigation.available', core, callback);
}
 
module.exports = admin;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/navigation/index.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/navigation/index.js

Statements: 9.52% (2 / 21)      Branches: 0% (0 / 10)      Functions: 0% (0 / 4)      Lines: 9.52% (2 / 21)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42    2 2                                                                            
"use strict";
 
var nconf = require('nconf');
var admin = require('./admin');
var translator = require('../../public/src/modules/translator');
 
var navigation = {};
 
navigation.get = function (callback) {
	if (admin.cache) {
		return callback(null, admin.cache);
	}
 
	admin.get(function (err, data) {
		if (err) {
			return callback(err);
		}
 
		data = data.filter(function (item) {
			return item && item.enabled;
		}).map(function (item) {
			if (!item.route.startsWith('http')) {
				item.route = nconf.get('relative_path') + item.route;
			}
 
			for (var i in item) {
				if (item.hasOwnProperty(i)) {
					item[i] = translator.unescape(item[i]);
				}
			}
			return item;
		});
 
		admin.cache = data;
 
		callback(null, data);
	});
};
 
 
module.exports = navigation;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins/

Statements: 5.96% (17 / 285)      Branches: 0% (0 / 151)      Functions: 0% (0 / 65)      Lines: 5.96% (17 / 285)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/plugins/
File Statements Branches Functions Lines
hooks.js 6.59% (6 / 91) 0% (0 / 62) 0% (0 / 17) 6.59% (6 / 91)
load.js 5.67% (11 / 194) 0% (0 / 89) 0% (0 / 48) 5.67% (11 / 194)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins/hooks.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins/hooks.js

Statements: 6.59% (6 / 91)      Branches: 0% (0 / 62)      Functions: 0% (0 / 17)      Lines: 6.59% (6 / 91)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180    1     1                           1                                                                                                                                                                 1                                             1                                   1                                                                            
'use strict';
 
var winston = require('winston'),
	async = require('async');
 
module.exports = function (Plugins) {
	Plugins.deprecatedHooks = {
		'filter:user.custom_fields': null,	// remove in v1.1.0
		'filter:post.save': 'filter:post.create',
		'filter:user.profileLinks': 'filter:user.profileMenu'
	};
	/*
		`data` is an object consisting of (* is required):
			`data.hook`*, the name of the NodeBB hook
			`data.method`*, the method called in that plugin
			`data.priority`, the relative priority of the method when it is eventually called (default: 10)
	*/
	Plugins.registerHook = function (id, data, callback) {
		callback = callback || function () {};
		function register() {
			Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || [];
			Plugins.loadedHooks[data.hook].push(data);
 
			callback();
		}
 
		if (!data.hook) {
			winston.warn('[plugins/' + id + '] registerHook called with invalid data.hook', data);
			return callback();
		}
 
		var method;
 
		if (Object.keys(Plugins.deprecatedHooks).indexOf(data.hook) !== -1) {
			winston.warn('[plugins/' + id + '] Hook `' + data.hook + '` is deprecated, ' +
				(Plugins.deprecatedHooks[data.hook] ?
					'please use `' + Plugins.deprecatedHooks[data.hook] + '` instead.' :
					'there is no alternative.'
				)
			);
		} else {
			// handle hook's startsWith, i.e. action:homepage.get
			var parts = data.hook.split(':');
			if (parts.length > 2) {
				parts.pop();
			}
			var hook = parts.join(':');
		}
 
		if (data.hook && data.method) {
			data.id = id;
			if (!data.priority) {
				data.priority = 10;
			}
 
			if (typeof data.method === 'string' && data.method.length > 0) {
				method = data.method.split('.').reduce(function (memo, prop) {
					if (memo && memo[prop]) {
						return memo[prop];
					} else {
						// Couldn't find method by path, aborting
						return null;
					}
				}, Plugins.libraries[data.id]);
 
				// Write the actual method reference to the hookObj
				data.method = method;
 
				register();
			} else if (typeof data.method === 'function') {
				register();
			} else {
				winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method);
				return callback();
			}
		}
	};
 
	Plugins.fireHook = function (hook, params, callback) {
		callback = typeof callback === 'function' ? callback : function () {};
 
		var hookList = Plugins.loadedHooks[hook];
		var hookType = hook.split(':')[0];
 
		switch (hookType) {
			case 'filter':
				fireFilterHook(hook, hookList, params, callback);
				break;
			case 'action':
				fireActionHook(hook, hookList, params, callback);
				break;
			case 'static':
				fireStaticHook(hook, hookList, params, callback);
				break;
			default:
				winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
				break;
		}
	};
 
	function fireFilterHook(hook, hookList, params, callback) {
		if (!Array.isArray(hookList) || !hookList.length) {
			return callback(null, params);
		}
 
		async.reduce(hookList, params, function (params, hookObj, next) {
			if (typeof hookObj.method !== 'function') {
				if (global.env === 'development') {
					winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
				}
				return next(null, params);
			}
 
			hookObj.method(params, next);
		}, function (err, values) {
			if (err) {
				winston.error('[plugins] ' + hook + ',  ' + err.message);
			}
 
			callback(err, values);
		});
	}
 
	function fireActionHook(hook, hookList, params, callback) {
		if (!Array.isArray(hookList) || !hookList.length) {
			return callback();
		}
		async.each(hookList, function (hookObj, next) {
 
			if (typeof hookObj.method !== 'function') {
				if (global.env === 'development') {
					winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
				}
				return next();
			}
 
			hookObj.method(params);
			next();
		}, callback);
	}
 
	function fireStaticHook(hook, hookList, params, callback) {
		if (!Array.isArray(hookList) || !hookList.length) {
			return callback();
		}
		async.each(hookList, function (hookObj, next) {
			if (typeof hookObj.method === 'function') {
				var timedOut = false;
 
				var timeoutId = setTimeout(function () {
					winston.warn('[plugins] Callback timed out, hook \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
					timedOut = true;
					next();
				}, 5000);
 
				try {
					hookObj.method(params, function () {
						clearTimeout(timeoutId);
						if (!timedOut) {
							next.apply(null, arguments);
						}
					});
				} catch(err) {
					winston.error('[plugins] Error executing \'' + hook + '\' in plugin \'' + hookObj.id + '\'');
					winston.error(err);
					clearTimeout(timeoutId);
					next();
				}
			} else {
				next();
			}
		}, callback);
	}
 
	Plugins.hasListeners = function (hook) {
		return !!(Plugins.loadedHooks[hook] && Plugins.loadedHooks[hook].length > 0);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins/load.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/plugins/load.js

Statements: 5.67% (11 / 194)      Branches: 0% (0 / 89)      Functions: 0% (0 / 48)      Lines: 5.67% (11 / 194)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374    2                                                                                                                                                                                                             1 1                             1                                                   1 1                                                           1                         1                                               1                                                                                   1                                                                                                           1                                                                                                                            
'use strict';
 
var db = require('../database');
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var async = require('async');
var winston = require('winston');
var nconf = require('nconf');
var _ = require('underscore');
var file = require('../file');
 
var utils = require('../../public/src/utils');
var meta = require('../meta');
 
 
module.exports = function (Plugins) {
	Plugins.getPluginPaths = function (callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('plugins:active', 0, -1, next);
			},
			function (plugins, next) {
				if (!Array.isArray(plugins)) {
					return next();
				}
 
				plugins = plugins.filter(function (plugin) {
					return plugin && typeof plugin === 'string';
				}).map(function (plugin) {
					return path.join(__dirname, '../../node_modules/', plugin);
				});
 
				async.filter(plugins, file.exists, function (plugins) {
					next(null, plugins);
				});
			},
		], callback);
	};
 
	Plugins.prepareForBuild = function (callback) {
		async.waterfall([
			async.apply(Plugins.getPluginPaths),
			function (paths, next) {
				async.map(paths, function (path, next) {
					Plugins.loadPluginInfo(path, next);
				}, next);
			},
			function (plugins, next) {
				async.each(plugins, function (pluginData, next) {
					async.parallel([
						async.apply(mapFiles, pluginData, 'css', 'cssFiles'),
						async.apply(mapFiles, pluginData, 'less', 'lessFiles'),
						async.apply(mapClientSideScripts, pluginData)
					], next);
				}, next);
			}
		], callback);
	};
 
	Plugins.loadPlugin = function (pluginPath, callback) {
		Plugins.loadPluginInfo(pluginPath, function (err, pluginData) {
			if (err) {
				if (err.message === '[[error:parse-error]]') {
					return callback();
				}
				return callback(pluginPath.match('nodebb-theme') ? null : err);
			}
 
			checkVersion(pluginData);
 
			async.parallel([
				function (next) {
					registerHooks(pluginData, pluginPath, next);
				},
				function (next) {
					mapStaticDirectories(pluginData, pluginPath, next);
				},
				function (next) {
					mapFiles(pluginData, 'css', 'cssFiles', next);
				},
				function (next) {
					mapFiles(pluginData, 'less', 'lessFiles', next);
				},
				function (next) {
					mapClientSideScripts(pluginData, next);
				},
				function (next) {
					mapClientModules(pluginData, next);
				},
				function (next) {
					loadLanguages(pluginData, next);
				}
			], function (err) {
				if (err) {
					winston.verbose('[plugins] Could not load plugin : ' + pluginData.id);
					return callback(err);
				}
 
				winston.verbose('[plugins] Loaded plugin: ' + pluginData.id);
				callback();
			});
		});
	};
 
	function checkVersion(pluginData) {
		function add() {
			if (Plugins.versionWarning.indexOf(pluginData.id) === -1) {
				Plugins.versionWarning.push(pluginData.id);
			}
		}
 
		if (pluginData.nbbpm && pluginData.nbbpm.compatibility && semver.validRange(pluginData.nbbpm.compatibility)) {
			if (!semver.satisfies(nconf.get('version'), pluginData.nbbpm.compatibility)) {
				add();
			}
		} else {
			add();
		}
	}
 
	function registerHooks(pluginData, pluginPath, callback) {
		if (!pluginData.library) {
			return callback();
		}
 
		var libraryPath = path.join(pluginPath, pluginData.library);
 
		try {
			if (!Plugins.libraries[pluginData.id]) {
				Plugins.requireLibrary(pluginData.id, libraryPath);
			}
 
			if (Array.isArray(pluginData.hooks) && pluginData.hooks.length > 0) {
				async.each(pluginData.hooks, function (hook, next) {
					Plugins.registerHook(pluginData.id, hook, next);
				}, callback);
			} else {
				callback();
			}
		} catch(err) {
			winston.error(err.stack);
			winston.warn('[plugins] Unable to parse library for: ' + pluginData.id);
			callback();
		}
	}
 
	function mapStaticDirectories(pluginData, pluginPath, callback) {
		function mapStaticDirs(mappedPath, callback) {
			if (Plugins.staticDirs[mappedPath]) {
				winston.warn('[plugins/' + pluginData.id + '] Mapped path (' + mappedPath + ') already specified!');
				callback();
			} else if (!validMappedPath.test(mappedPath)) {
				winston.warn('[plugins/' + pluginData.id + '] Invalid mapped path specified: ' + mappedPath + '. Path must adhere to: ' + validMappedPath.toString());
				callback();
			} else {
				var realPath = pluginData.staticDirs[mappedPath];
				var staticDir = path.join(pluginPath, realPath);
 
				file.exists(staticDir, function (exists) {
					if (exists) {
						Plugins.staticDirs[pluginData.id + '/' + mappedPath] = staticDir;
					} else {
						winston.warn('[plugins/' + pluginData.id + '] Mapped path \'' + mappedPath + ' => ' + staticDir + '\' not found.');
					}
					callback();
				});
			}
		}
 
		var validMappedPath = /^[\w\-_]+$/;
 
		pluginData.staticDirs = pluginData.staticDirs || {};
 
		var dirs = Object.keys(pluginData.staticDirs);
		async.each(dirs, mapStaticDirs, callback);
	}
 
	function mapFiles(pluginData, type, globalArray, callback) {
		if (Array.isArray(pluginData[type])) {
			if (global.env === 'development') {
				winston.verbose('[plugins] Found ' + pluginData[type].length + ' ' + type + ' file(s) for plugin ' + pluginData.id);
			}
 
			Plugins[globalArray] = Plugins[globalArray].concat(pluginData[type].map(function (file) {
				return path.join(pluginData.id, file);
			}));
		}
		callback();
	}
 
	function mapClientSideScripts(pluginData, callback) {
		if (Array.isArray(pluginData.scripts)) {
			if (global.env === 'development') {
				winston.verbose('[plugins] Found ' + pluginData.scripts.length + ' js file(s) for plugin ' + pluginData.id);
			}
 
			Plugins.clientScripts = Plugins.clientScripts.concat(pluginData.scripts.map(function (file) {
				return resolveModulePath(path.join(__dirname, '../../node_modules/', pluginData.id, file), file);
			})).filter(Boolean);
		}
 
		if (Array.isArray(pluginData.acpScripts)) {
			if (global.env === 'development') {
				winston.verbose('[plugins] Found ' + pluginData.acpScripts.length + ' ACP js file(s) for plugin ' + pluginData.id);
			}
 
			Plugins.acpScripts = Plugins.acpScripts.concat(pluginData.acpScripts.map(function (file) {
				return resolveModulePath(path.join(__dirname, '../../node_modules/', pluginData.id, file), file);
			})).filter(Boolean);
		}
 
		callback();
	}
 
	function mapClientModules(pluginData, callback) {
		if (!pluginData.hasOwnProperty('modules')) {
			return callback();
		}
 
		var modules = {};
 
		if (Array.isArray(pluginData.modules)) {
			if (global.env === 'development') {
				winston.verbose('[plugins] Found ' + pluginData.modules.length + ' AMD-style module(s) for plugin ' + pluginData.id);
			}
 
			var strip = pluginData.hasOwnProperty('modulesStrip') ? parseInt(pluginData.modulesStrip, 10) : 0;
 
			pluginData.modules.forEach(function (file) {
				if (strip) {
					modules[file.replace(new RegExp('\.?(\/[^\/]+){' + strip + '}\/'), '')] = path.join('./node_modules/', pluginData.id, file);
				} else {
					modules[path.basename(file)] = path.join('./node_modules/', pluginData.id, file);
				}
			});
 
			meta.js.scripts.modules = _.extend(meta.js.scripts.modules, modules);
		} else {
			var keys = Object.keys(pluginData.modules);
 
			if (global.env === 'development') {
				winston.verbose('[plugins] Found ' + keys.length + ' AMD-style module(s) for plugin ' + pluginData.id);
			}
 
			for (var name in pluginData.modules) {
				if (pluginData.modules.hasOwnProperty(name)) {
					modules[name] = path.join('./node_modules/', pluginData.id, pluginData.modules[name]);
				}
			}
 
			meta.js.scripts.modules = _.extend(meta.js.scripts.modules, modules);
		}
 
		callback();
	}
 
	function loadLanguages(pluginData, callback) {
		if (typeof pluginData.languages !== 'string') {
			return callback();
		}
 
		var pathToFolder = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages);
		var defaultLang = (pluginData.defaultLang || 'en_GB').replace('_', '-').replace('@', '-x-');
 
		utils.walk(pathToFolder, function (err, languages) {
			if (err) {
				return callback(err);
			}
 
			async.each(languages, function (pathToLang, next) {
				fs.readFile(pathToLang, function (err, file) {
					if (err) {
						return next(err);
					}
					var data;
					var language = path.dirname(pathToLang).split(/[\/\\]/).pop().replace('_', '-').replace('@', '-x-');
					var namespace = path.basename(pathToLang, '.json');
					var langNamespace = language + '/' + namespace;
 
					try {
						data = JSON.parse(file.toString());
					} catch (err) {
						winston.error('[plugins] Unable to parse custom language file: ' + pathToLang + '\r\n' + err.stack);
						return next(err);
					}
 
					Plugins.customLanguages[langNamespace] = Plugins.customLanguages[langNamespace] || {};
					Object.assign(Plugins.customLanguages[langNamespace], data);
 
					if (defaultLang && defaultLang === language) {
						Plugins.languageCodes.filter(function (lang) {
							return defaultLang !== lang;
						}).forEach(function (lang) {
							var langNS = lang + '/' + namespace;
							Plugins.customLanguages[langNS] = Object.assign(Plugins.customLanguages[langNS] || {}, data);
						});
					}
 
					next();
				});
			}, function (err) {
				if (err) {
					return callback(err);
				}
 
				callback();
			});
		});
	}
 
	function resolveModulePath(fullPath, relPath) {
		/**
		  * With npm@3, dependencies can become flattened, and appear at the root level.
		  * This method resolves these differences if it can.
		  */
		var matches = fullPath.match(/node_modules/g);
		var atRootLevel = !matches || matches.length === 1;
 
		try {
			fs.statSync(fullPath);
			winston.verbose('[plugins/load] File found: ' + fullPath);
			return fullPath;
		} catch (e) {
			// File not visible to the calling process, ascend to root level if possible and try again
			if (!atRootLevel && relPath) {
				winston.verbose('[plugins/load] File not found: ' + fullPath + ' (Ascending)');
				return resolveModulePath(path.join(__dirname, '../..', relPath));
			} else {
				// Already at root level, file was simply not found
				winston.warn('[plugins/load] File not found: ' + fullPath + ' (Ignoring)');
				return null;
			}
		}
	}
 
	Plugins.loadPluginInfo = function (pluginPath, callback) {
		async.parallel({
			package: function (next) {
				fs.readFile(path.join(pluginPath, 'package.json'), next);
			},
			plugin: function (next) {
				fs.readFile(path.join(pluginPath, 'plugin.json'), next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var pluginData;
			var packageData;
			try {
				pluginData = JSON.parse(results.plugin);
				packageData = JSON.parse(results.package);
 
				pluginData.id = packageData.name;
				pluginData.name = packageData.name;
				pluginData.description = packageData.description;
				pluginData.version = packageData.version;
				pluginData.repository = packageData.repository;
				pluginData.nbbpm = packageData.nbbpm;
			} catch(err) {
				var pluginDir = pluginPath.split(path.sep);
				pluginDir = pluginDir[pluginDir.length - 1];
 
				winston.error('[plugins/' + pluginDir + '] Error in plugin.json or package.json! ' + err.message);
 
				return callback(new Error('[[error:parse-error]]'));
			}
			callback(null, pluginData);
		});
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/

Statements: 6.55% (62 / 947)      Branches: 0% (0 / 448)      Functions: 0% (0 / 333)      Lines: 6.57% (62 / 943)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/posts/
File Statements Branches Functions Lines
bookmarks.js 6.38% (3 / 47) 0% (0 / 26) 0% (0 / 16) 6.52% (3 / 46)
cache.js 40% (2 / 5) 0% (0 / 2) 0% (0 / 1) 40% (2 / 5)
category.js 7.32% (3 / 41) 0% (0 / 16) 0% (0 / 17) 7.32% (3 / 41)
create.js 6.25% (3 / 48) 0% (0 / 22) 0% (0 / 16) 6.25% (3 / 48)
delete.js 7.2% (9 / 125) 0% (0 / 22) 0% (0 / 71) 7.2% (9 / 125)
edit.js 7.14% (5 / 70) 0% (0 / 20) 0% (0 / 21) 7.14% (5 / 70)
flags.js 2.76% (5 / 181) 0% (0 / 90) 0% (0 / 61) 2.76% (5 / 181)
parse.js 12.77% (6 / 47) 0% (0 / 28) 0% (0 / 6) 12.77% (6 / 47)
recent.js 5% (1 / 20) 0% (0 / 8) 0% (0 / 11) 5% (1 / 20)
summary.js 9.41% (8 / 85) 0% (0 / 42) 0% (0 / 22) 9.41% (8 / 85)
tools.js 8.33% (3 / 36) 0% (0 / 18) 0% (0 / 12) 8.33% (3 / 36)
topics.js 5.71% (2 / 35) 0% (0 / 14) 0% (0 / 21) 5.71% (2 / 35)
user.js 4.11% (3 / 73) 0% (0 / 55) 0% (0 / 22) 4.23% (3 / 71)
votes.js 6.72% (9 / 134) 0% (0 / 85) 0% (0 / 36) 6.77% (9 / 133)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/bookmarks.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/bookmarks.js

Statements: 6.38% (3 / 47)      Branches: 0% (0 / 26)      Functions: 0% (0 / 16)      Lines: 6.52% (3 / 46)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109    2   2                         1                                                                                                                                                                                      
'use strict';
 
var async = require('async');
 
var db = require('../database');
var plugins = require('../plugins');
 
module.exports = function (Posts) {
 
	Posts.bookmark = function (pid, uid, callback) {
		toggleBookmark('bookmark', pid, uid, callback);
	};
 
	Posts.unbookmark = function (pid, uid, callback) {
		toggleBookmark('unbookmark', pid, uid, callback);
	};
 
	function toggleBookmark(type, pid, uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(new Error('[[error:not-logged-in]]'));
		}
		var isBookmarking = type === 'bookmark';
 
		async.parallel({
			owner: function (next) {
				Posts.getPostField(pid, 'uid', next);
			},
			postData: function (next) {
				Posts.getPostFields(pid, ['pid', 'uid'], next);
			},
			hasBookmarked: function (next) {
				Posts.hasBookmarked(pid, uid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (isBookmarking && results.hasBookmarked) {
				return callback(new Error('[[error:already-bookmarked]]'));
			}
 
			if (!isBookmarking && !results.hasBookmarked) {
				return callback(new Error('[[error:already-unbookmarked]]'));
			}
 
			async.waterfall([
				function (next) {
					if (isBookmarking) {
						db.sortedSetAdd('uid:' + uid + ':bookmarks', Date.now(), pid, next);
					} else {
						db.sortedSetRemove('uid:' + uid + ':bookmarks', pid, next);
					}
				},
				function (next) {
					db[isBookmarking ? 'setAdd' : 'setRemove']('pid:' + pid + ':users_bookmarked', uid, next);
				},
				function (next) {
					db.setCount('pid:' + pid + ':users_bookmarked', next);
				},
				function (count, next) {
					results.postData.bookmarks = count;
					Posts.setPostField(pid, 'bookmarks', count, next);
				}
			], function (err) {
				if (err) {
					return callback(err);
				}
 
				var current = results.hasBookmarked ? 'bookmarked' : 'unbookmarked';
 
				plugins.fireHook('action:post.' + type, {
					pid: pid,
					uid: uid,
					owner: results.owner,
					current: current
				});
 
				callback(null, {
					post: results.postData,
					isBookmarked: isBookmarking
				});
			});
		});
	}
 
	Posts.hasBookmarked = function (pid, uid, callback) {
		if (!parseInt(uid, 10)) {
			if (Array.isArray(pid)) {
				callback(null, pid.map(function () { return false; }));
			} else {
				callback(null, false);
			}
			return;
		}
 
		if (Array.isArray(pid)) {
			var sets = pid.map(function (pid) {
				return 'pid:' + pid + ':users_bookmarked';
			});
 
			db.isMemberOfSets(sets, uid, callback);
		} else {
			db.isSetMember('pid:' + pid + ':users_bookmarked', uid, callback);
		}
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/cache.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/cache.js

Statements: 40% (2 / 5)      Branches: 0% (0 / 2)      Functions: 0% (0 / 1)      Lines: 40% (2 / 5)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 112 2                  
var	LRU = require('lru-cache');
var meta = require('../meta');
 
var cache = LRU({
	max: parseInt(meta.config.postCacheSize, 10) || 1048576,
	length: function (n) { return n.length; },
	maxAge: 1000 * 60 * 60
});
 
module.exports = cache;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/category.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/category.js

Statements: 7.32% (3 / 41)      Branches: 0% (0 / 16)      Functions: 0% (0 / 17)      Lines: 7.32% (3 / 41)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86      2 2   2                                                                                                                                                              
 
'use strict';
 
var async = require('async');
var _ = require('underscore');
 
var db = require('../database');
var topics = require('../topics');
 
module.exports = function (Posts) {
 
	Posts.getCidByPid = function (pid, callback) {
		async.waterfall([
			function (next) {
				Posts.getPostField(pid, 'tid', next);
			},
			function (tid, next) {
				topics.getTopicField(tid, 'cid', next);
			}
		], callback);
	};
 
	Posts.getCidsByPids = function (pids, callback) {
		var tids;
		var postData;
		async.waterfall([
			function (next) {
				Posts.getPostsFields(pids, ['tid'], next);
			},
			function (_postData, next) {
				postData = _postData;
				tids = postData.map(function (post) {
					return post.tid;
				}).filter(function (tid, index, array) {
					return tid && array.indexOf(tid) === index;
				});
 
				topics.getTopicsFields(tids, ['cid'], next);
			},
			function (topicData, next) {
				var map = {};
				topicData.forEach(function (topic, index) {
					if (topic) {
						map[tids[index]] = topic.cid;
					}
				});
 
				var cids = postData.map(function (post) {
					return map[post.tid];
				});
				next(null, cids);
			}
		], callback);
	};
 
	Posts.filterPidsByCid = function (pids, cid, callback) {
		if (!cid) {
			return callback(null, pids);
		}
 
		if (!Array.isArray(cid) || cid.length === 1) {
			// Single cid
			db.isSortedSetMembers('cid:' + parseInt(cid, 10) + ':pids', pids, function (err, isMembers) {
				if (err) {
					return callback(err);
				}
				pids = pids.filter(function (pid, index) {
					return pid && isMembers[index];
				});
				callback(null, pids);
			});
		} else {
			// Multiple cids
			async.map(cid, function (cid, next) {
				Posts.filterPidsByCid(pids, cid, next);
			}, function (err, pidsArr) {
				if (err) {
					return callback(err);
				}
 
				callback(null, _.union.apply(_, pidsArr));
			});
		}
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/create.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/create.js

Statements: 6.25% (3 / 48)      Branches: 0% (0 / 22)      Functions: 0% (0 / 16)      Lines: 6.25% (3 / 48)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114    2 2   2                                                                                                                                                                                                                        
'use strict';
 
var async = require('async');
var _ = require('underscore');
 
var meta = require('../meta');
var db = require('../database');
var plugins = require('../plugins');
var user = require('../user');
var topics = require('../topics');
var categories = require('../categories');
 
 
module.exports = function (Posts) {
 
	Posts.create = function (data, callback) {
		// This is an internal method, consider using Topics.reply instead
		var uid = data.uid;
		var tid = data.tid;
		var content = data.content.toString();
		var timestamp = data.timestamp || Date.now();
 
		if (!uid && parseInt(uid, 10) !== 0) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		var postData;
 
		async.waterfall([
			function (next) {
				db.incrObjectField('global', 'nextPid', next);
			},
			function (pid, next) {
 
				postData = {
					'pid': pid,
					'uid': uid,
					'tid': tid,
					'content': content,
					'timestamp': timestamp,
					'deleted': 0
				};
 
				if (data.toPid) {
					postData.toPid = data.toPid;
				}
 
				if (data.ip && parseInt(meta.config.trackIpPerPost, 10) === 1) {
					postData.ip = data.ip;
				}
 
				if (data.handle && !parseInt(uid, 10)) {
					postData.handle = data.handle;
				}
 
				plugins.fireHook('filter:post.save', postData, next);
			},
			function (postData, next) {
				plugins.fireHook('filter:post.create', {post: postData, data: data}, next);
			},
			function (data, next) {
				postData = data.post;
				db.setObject('post:' + postData.pid, postData, next);
			},
			function (next) {
				async.parallel([
					function (next) {
						user.onNewPostMade(postData, next);
					},
					function (next) {
						topics.onNewPostMade(postData, next);
					},
					function (next) {
						topics.getTopicFields(tid, ['cid', 'pinned'], function (err, topicData) {
							if (err) {
								return next(err);
							}
							postData.cid = topicData.cid;
							categories.onNewPostMade(topicData.cid, topicData.pinned, postData, next);
						});
					},
					function (next) {
						db.sortedSetAdd('posts:pid', timestamp, postData.pid, next);
					},
					function (next) {
						if (!postData.toPid) {
							return next(null);
						}
						async.parallel([
							async.apply(db.sortedSetAdd, 'pid:' + postData.toPid + ':replies', timestamp, postData.pid),
							async.apply(db.incrObjectField, 'post:' + postData.toPid, 'replies')
						], next);
					},
					function (next) {
						db.incrObjectField('global', 'postCount', next);
					}
				], function (err) {
					if (err) {
						return next(err);
					}
					plugins.fireHook('filter:post.get', postData, next);
				});
			},
			function (postData, next) {
				plugins.fireHook('action:post.save', _.clone(postData));
				next(null, postData);
			}
		], callback);
	};
};
 
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/delete.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/delete.js

Statements: 7.2% (9 / 125)      Branches: 0% (0 / 22)      Functions: 0% (0 / 71)      Lines: 7.2% (9 / 125)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301    2 2   2                                                                                                                                                                 1                                                                                                                                                   1                                                                                                         1                           1                                       1                                                                     1                                    
'use strict';
 
var async = require('async');
var _ = require('underscore');
 
var db = require('../database');
var topics = require('../topics');
var user = require('../user');
var notifications = require('../notifications');
var plugins = require('../plugins');
 
module.exports = function (Posts) {
 
	Posts.delete = function (pid, uid, callback) {
		var postData;
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:post.delete', {pid: pid, uid: uid}, next);
			},
			function (data, next) {
				Posts.setPostFields(pid, {deleted: 1, deleterUid: uid}, next);
			},
			function (next) {
				Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'timestamp'], next);
			},
			function (_post, next) {
				postData = _post;
				topics.getTopicFields(_post.tid, ['tid', 'cid', 'pinned'], next);
			},
			function (topicData, next) {
				async.parallel([
					function (next) {
						updateTopicTimestamp(topicData, next);
					},
					function (next) {
						db.sortedSetRemove('cid:' + topicData.cid + ':pids', pid, next);
					},
					function (next) {
						topics.updateTeaser(postData.tid, next);
					}
				], next);
			},
			function (results, next) {
				plugins.fireHook('action:post.delete', pid);
				next(null, postData);
			}
		], callback);
	};
 
	Posts.restore = function (pid, uid, callback) {
		var postData;
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:post.restore', {pid: pid, uid: uid}, next);
			},
			function (data, next) {
				Posts.setPostFields(pid, {deleted: 0, deleterUid: 0}, next);
			},
			function (next) {
				Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'timestamp'], next);
			},
			function (_post, next) {
				postData = _post;
				topics.getTopicFields(_post.tid, ['tid', 'cid', 'pinned'], next);
			},
			function (topicData, next) {
				postData.cid = topicData.cid;
				async.parallel([
					function (next) {
						updateTopicTimestamp(topicData, next);
					},
					function (next) {
						db.sortedSetAdd('cid:' + topicData.cid + ':pids', postData.timestamp, pid, next);
					},
					function (next) {
						topics.updateTeaser(postData.tid, next);
					}
				], next);
			},
			function (results, next) {
				plugins.fireHook('action:post.restore', _.clone(postData));
				next(null, postData);
			}
		], callback);
	};
 
	function updateTopicTimestamp(topicData, callback) {
		var timestamp;
		async.waterfall([
			function (next) {
				topics.getLatestUndeletedPid(topicData.tid, next);
			},
			function (pid, next) {
				if (!parseInt(pid, 10)) {
					return callback();
				}
				Posts.getPostField(pid, 'timestamp', next);
			},
			function (_timestamp, next) {
				timestamp = _timestamp;
				if (!parseInt(timestamp, 10)) {
					return callback();
				}
				topics.updateTimestamp(topicData.tid, timestamp, next);
			},
			function (next) {
				if (parseInt(topicData.pinned, 10) !== 1) {
					db.sortedSetAdd('cid:' + topicData.cid + ':tids', timestamp, topicData.tid, next);
				} else {
					next();
				}
			}
		], callback);
	}
 
	Posts.purge = function (pid, uid, callback) {
		async.waterfall([
			function (next) {
				Posts.exists(pid, next);
			},
			function (exists, next) {
				if (!exists) {
					return callback();
				}
				plugins.fireHook('filter:post.purge', {pid: pid, uid: uid}, next);
			},
			function (data, next) {
				async.parallel([
					function (next) {
						deletePostFromTopicUserNotification(pid, next);
					},
					function (next) {
						deletePostFromCategoryRecentPosts(pid, next);
					},
					function (next) {
						deletePostFromUsersBookmarks(pid, next);
					},
					function (next) {
						deletePostFromUsersVotes(pid, next);
					},
					function (next) {
						deletePostFromReplies(pid, next);
					},
					function (next) {
						db.sortedSetsRemove(['posts:pid', 'posts:flagged'], pid, next);
					},
					function (next) {
						Posts.dismissFlag(pid, next);
					}
				], function (err) {
					if (err) {
						return next(err);
					}
					plugins.fireHook('action:post.purge', pid);
					db.delete('post:' + pid, next);
				});
			}
		], callback);
	};
 
	function deletePostFromTopicUserNotification(pid, callback) {
		var postData;
		async.waterfall([
			function (next) {
				Posts.getPostFields(pid, ['tid', 'uid'], next);
			},
			function (_postData, next) {
				postData = _postData;
				db.sortedSetsRemove([
					'tid:' + postData.tid + ':posts',
					'tid:' + postData.tid + ':posts:votes',
					'uid:' + postData.uid + ':posts'
				], pid, next);
			},
			function (next) {
				topics.getTopicFields(postData.tid, ['tid', 'cid', 'pinned'], next);
			},
			function (topicData, next) {
				async.parallel([
					function (next) {
						db.decrObjectField('global', 'postCount', next);
					},
					function (next) {
						db.decrObjectField('category:' + topicData.cid, 'post_count', next);
					},
					function (next) {
						topics.decreasePostCount(postData.tid, next);
					},
					function (next) {
						topics.updateTeaser(postData.tid, next);
					},
					function (next) {
						updateTopicTimestamp(topicData, next);
					},
					function (next) {
						db.sortedSetIncrBy('cid:' + topicData.cid + ':tids:posts', -1, postData.tid, next);
					},
					function (next) {
						db.sortedSetIncrBy('tid:' + postData.tid + ':posters', -1, postData.uid, next);
					},
					function (next) {
						user.incrementUserPostCountBy(postData.uid, -1, next);
					},
					function (next) {
						notifications.rescind('new_post:tid:' + postData.tid + ':pid:' + pid + ':uid:' + postData.uid, next);
					}
				], next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	function deletePostFromCategoryRecentPosts(pid, callback) {
		db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
			if (err) {
				return callback(err);
			}
 
			var sets = cids.map(function (cid) {
				return 'cid:' + cid + ':pids';
			});
 
			db.sortedSetsRemove(sets, pid, callback);
		});
	}
 
	function deletePostFromUsersBookmarks(pid, callback) {
		db.getSetMembers('pid:' + pid + ':users_bookmarked', function (err, uids) {
			if (err) {
				return callback(err);
			}
 
			var sets = uids.map(function (uid) {
				return 'uid:' + uid + ':bookmarks';
			});
 
			db.sortedSetsRemove(sets, pid, function (err) {
				if (err) {
					return callback(err);
				}
 
				db.delete('pid:' + pid + ':users_bookmarked', callback);
			});
		});
	}
 
	function deletePostFromUsersVotes(pid, callback) {
		async.parallel({
			upvoters: function (next) {
				db.getSetMembers('pid:' + pid + ':upvote', next);
			},
			downvoters: function (next) {
				db.getSetMembers('pid:' + pid + ':downvote', next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var upvoterSets = results.upvoters.map(function (uid) {
				return 'uid:' + uid + ':upvote';
			});
 
			var downvoterSets = results.downvoters.map(function (uid) {
				return 'uid:' + uid + ':downvote';
			});
 
			async.parallel([
				function (next) {
					db.sortedSetsRemove(upvoterSets, pid, next);
				},
				function (next) {
					db.sortedSetsRemove(downvoterSets, pid, next);
				},
				function (next) {
					db.deleteAll(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], next);
				}
			], callback);
		});
	}
 
	function deletePostFromReplies(pid, callback) {
		Posts.getPostField(pid, 'toPid', function (err, toPid) {
			if (err) {
				return callback(err);
			}
			if (!parseInt(toPid, 10)) {
				return callback(null);
			}
			async.parallel([
				async.apply(db.sortedSetRemove, 'pid:' + toPid + ':replies', pid),
				async.apply(db.decrObjectField, 'post:' + toPid, 'replies')
			], callback);
		});
	}
 
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/edit.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/edit.js

Statements: 7.14% (5 / 70)      Branches: 0% (0 / 20)      Functions: 0% (0 / 21)      Lines: 7.14% (5 / 70)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160    2 2 2   2                                                                                                                                                       1                                                                                                                                                          
'use strict';
 
var async = require('async');
var validator = require('validator');
var _ = require('underscore');
 
var db = require('../database');
var topics = require('../topics');
var user = require('../user');
var privileges = require('../privileges');
var plugins = require('../plugins');
var cache = require('./cache');
var pubsub = require('../pubsub');
var utils = require('../../public/src/utils');
 
module.exports = function (Posts) {
 
	pubsub.on('post:edit', function (pid) {
		cache.del(pid);
	});
 
	Posts.edit = function (data, callback) {
		var postData;
		var results;
 
		async.waterfall([
			function (next) {
				privileges.posts.canEdit(data.pid, data.uid, next);
			},
			function (canEdit, next) {
				if (!canEdit.flag) {
					return next(new Error(canEdit.message));
				}
				Posts.getPostData(data.pid, next);
			},
			function (_postData, next) {
				if (!_postData) {
					return next(new Error('[[error:no-post]]'));
				}
 
				postData = _postData;
				postData.content = data.content;
				postData.edited = Date.now();
				postData.editor = data.uid;
				if (data.handle) {
					postData.handle = data.handle;
				}
				plugins.fireHook('filter:post.edit', {req: data.req, post: postData, data: data, uid: data.uid}, next);
			},
			function (result, next) {
				postData = result.post;
				Posts.setPostFields(data.pid, postData, next);
			},
			function (next) {
				async.parallel({
					editor: function (next) {
						user.getUserFields(data.uid, ['username', 'userslug'], next);
					},
					topic: function (next) {
						editMainPost(data, postData, next);
					}
				}, next);
			},
			function (_results, next) {
				results = _results;
 
				postData.cid = results.topic.cid;
				postData.topic = results.topic;
				plugins.fireHook('action:post.edit', _.clone(postData));
 
				cache.del(String(postData.pid));
				pubsub.publish('post:edit', String(postData.pid));
 
				Posts.parsePost(postData, next);
			},
			function (postData, next) {
				results.post = postData;
				next(null, results);
			}
		], callback);
	};
 
	function editMainPost(data, postData, callback) {
		var tid = postData.tid;
		var title = data.title ? data.title.trim() : '';
 
		async.parallel({
			topic: function (next) {
				topics.getTopicFields(tid, ['cid', 'title'], next);
			},
			isMain: function (next) {
				Posts.isMain(data.pid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (!results.isMain) {
				return callback(null, {
					tid: tid,
					cid: results.topic.cid,
					isMainPost: false,
					renamed: false
				});
			}
 
			var topicData = {
				tid: tid,
				cid: results.topic.cid,
				uid: postData.uid,
				mainPid: data.pid
			};
 
			if (title) {
				topicData.title = title;
				topicData.slug = tid + '/' + (utils.slugify(title) || 'topic');
			}
 
			topicData.thumb = data.thumb || '';
 
			data.tags = data.tags || [];
 
			async.waterfall([
				function (next) {
					plugins.fireHook('filter:topic.edit', {req: data.req, topic: topicData, data: data}, next);
				},
				function (results, next) {
					db.setObject('topic:' + tid, results.topic, next);
				},
				function (next) {
					topics.updateTags(tid, data.tags, next);
				},
				function (next) {
					topics.getTopicTagsObjects(tid, next);
				},
				function (tags, next) {
					topicData.tags = data.tags;
					topicData.oldTitle = results.topic.title;
					plugins.fireHook('action:topic.edit', topicData);
					next(null, {
						tid: tid,
						cid: results.topic.cid,
						uid: postData.uid,
						title: validator.escape(String(title)),
						oldTitle: results.topic.title,
						slug: topicData.slug,
						isMainPost: true,
						renamed: title !== results.topic.title,
						tags: tags
					});
				}
			], callback);
		});
	}
 
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/flags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/flags.js

Statements: 2.76% (5 / 181)      Branches: 0% (0 / 90)      Functions: 0% (0 / 61)      Lines: 2.76% (5 / 181)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419        2 2 2                                                                                                                                         1                                                                                                                                                                                                                                                           1                                                                                                                                                                                                                                                                                                                                                                                                                                                  
 
 
'use strict';
 
var async = require('async');
var winston = require('winston');
var db = require('../database');
var user = require('../user');
var analytics = require('../analytics');
 
module.exports = function (Posts) {
 
	Posts.flag = function (post, uid, reason, callback) {
		if (!parseInt(uid, 10) || !reason) {
			return callback();
		}
 
		async.waterfall([
			function (next) {
				async.parallel({
					hasFlagged: async.apply(Posts.isFlaggedByUser, post.pid, uid),
					exists: async.apply(Posts.exists, post.pid)
				}, next);
			},
			function (results, next) {
				if (!results.exists) {
					return next(new Error('[[error:no-post]]'));
				}
 
				if (results.hasFlagged) {
					return next(new Error('[[error:already-flagged]]'));
				}
 
				var now = Date.now();
				async.parallel([
					function (next) {
						db.sortedSetAdd('posts:flagged', now, post.pid, next);
					},
					function (next) {
						db.sortedSetIncrBy('posts:flags:count', 1, post.pid, next);
					},
					function (next) {
						db.incrObjectField('post:' + post.pid, 'flags', next);
					},
					function (next) {
						db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next);
					},
					function (next) {
						db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next);
					},
					function (next) {
						if (parseInt(post.uid, 10)) {
							async.parallel([
								async.apply(db.sortedSetIncrBy, 'users:flags', 1, post.uid),
								async.apply(db.incrObjectField, 'user:' + post.uid, 'flags'),
								async.apply(db.sortedSetAdd, 'uid:' + post.uid + ':flag:pids', now, post.pid)
							], next);
						} else {
							next();
						}
					}
				], next);
			},
			function (data, next) {
				openNewFlag(post.pid, uid, next);
			}
		], function (err) {
			if (err) {
				return callback(err);
			}
			analytics.increment('flags');
			callback();
		});
	};
 
	function openNewFlag(pid, uid, callback) {
		db.sortedSetScore('posts:flags:count', pid, function (err, count) {
			if (err) {
				return callback(err);
			}
			if (count === 1) {	// Only update state on new flag
				Posts.updateFlagData(uid, pid, {
					state: 'open'
				}, callback);
			} else {
				callback();
			}
		});
	}
 
	Posts.isFlaggedByUser = function (pid, uid, callback) {
		db.isSortedSetMember('pid:' + pid + ':flag:uids', uid, callback);
	};
 
	Posts.dismissFlag = function (pid, callback) {
		async.waterfall([
			function (next) {
				db.getObjectFields('post:' + pid, ['pid', 'uid', 'flags'], next);
			},
			function (postData, next) {
				if (!postData.pid) {
					return callback();
				}
				async.parallel([
					function (next) {
						if (parseInt(postData.uid, 10)) {
							if (parseInt(postData.flags, 10) > 0) {
								async.parallel([
									async.apply(db.sortedSetIncrBy, 'users:flags', -postData.flags, postData.uid),
									async.apply(db.incrObjectFieldBy, 'user:' + postData.uid, 'flags', -postData.flags)
								], next);
							} else {
								next();
							}
						} else {
							next();
						}
					},
					function (next) {
						db.sortedSetsRemove([
							'posts:flagged',
							'posts:flags:count',
							'uid:' + postData.uid + ':flag:pids'
						], pid, next);
					},
					function (next) {
						async.series([
							function (next) {
								db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function (err, uids) {
									if (err) {
										return next(err);
									}
 
									async.each(uids, function (uid, next) {
										var nid = 'post_flag:' + pid + ':uid:' + uid;
										async.parallel([
											async.apply(db.delete, 'notifications:' + nid),
											async.apply(db.sortedSetRemove, 'notifications', 'post_flag:' + pid + ':uid:' + uid)
										], next);
									}, next);
								});
							},
							async.apply(db.delete, 'pid:' + pid + ':flag:uids')
						], next);
					},
					async.apply(db.deleteObjectField, 'post:' + pid, 'flags'),
					async.apply(db.delete, 'pid:' + pid + ':flag:uid:reason'),
					async.apply(db.deleteObjectFields, 'post:' + pid, ['flag:state', 'flag:assignee', 'flag:notes', 'flag:history'])
				], next);
			},
			function (results, next) {
				db.sortedSetsRemoveRangeByScore(['users:flags'], '-inf', 0, next);
			}
		], callback);
	};
 
	Posts.dismissAllFlags = function (callback) {
		db.getSortedSetRange('posts:flagged', 0, -1, function (err, pids) {
			if (err) {
				return callback(err);
			}
			async.eachSeries(pids, Posts.dismissFlag, callback);
		});
	};
 
	Posts.dismissUserFlags = function (uid, callback) {
		db.getSortedSetRange('uid:' + uid + ':flag:pids', 0, -1, function (err, pids) {
			if (err) {
				return callback(err);
			}
			async.eachSeries(pids, Posts.dismissFlag, callback);
		});
	};
 
	Posts.getFlags = function (set, cid, uid, start, stop, callback) {
		async.waterfall([
			function (next) {
				if (Array.isArray(set)) {
					db.getSortedSetRevIntersect({sets: set, start: start, stop: -1, aggregate: 'MAX'}, next);
				} else {
					db.getSortedSetRevRange(set, start, -1, next);
				}
			},
			function (pids, next) {
				if (cid) {
					Posts.filterPidsByCid(pids, cid, next);
				} else {
					process.nextTick(next, null, pids);
				}
			},
			function (pids, next) {
				getFlaggedPostsWithReasons(pids, uid, next);
			},
			function (posts, next) {
				var count = posts.length;
				var end = stop - start + 1;
				next(null, {posts: posts.slice(0, stop === -1 ? undefined : end), count: count});
			}
		], callback);
	};
 
	function getFlaggedPostsWithReasons(pids, uid, callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					uidsReasons: function (next) {
						async.map(pids, function (pid, next) {
							db.getSortedSetRange('pid:' + pid + ':flag:uid:reason', 0, -1, next);
						}, next);
					},
					posts: function (next) {
						Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags', 'flag:assignee', 'flag:state', 'flag:notes', 'flag:history']}, next);
					}
				}, next);
			},
			function (results, next) {
				async.map(results.uidsReasons, function (uidReasons, next) {
					async.map(uidReasons, function (uidReason, next) {
						var uid = uidReason.split(':')[0];
						var reason = uidReason.substr(uidReason.indexOf(':') + 1);
						user.getUserFields(uid, ['username', 'userslug', 'picture'], function (err, userData) {
							next(err, {user: userData, reason: reason});
						});
					}, next);
				}, function (err, reasons) {
					if (err) {
						return callback(err);
					}
 
					results.posts.forEach(function (post, index) {
						if (post) {
							post.flagReasons = reasons[index];
						}
					});
 
					next(null, results.posts);
				});
			},
			async.apply(Posts.expandFlagHistory),
			function (posts, next) {
				// Parse out flag data into its own object inside each post hash
				async.map(posts, function (postObj, next) {
					for(var prop in postObj) {
						postObj.flagData = postObj.flagData || {};
 
						if (postObj.hasOwnProperty(prop) && prop.startsWith('flag:')) {
							postObj.flagData[prop.slice(5)] = postObj[prop];
 
							if (prop === 'flag:state') {
								switch(postObj[prop]) {
									case 'open':
										postObj.flagData.labelClass = 'info';
										break;
									case 'wip':
										postObj.flagData.labelClass = 'warning';
										break;
									case 'resolved':
										postObj.flagData.labelClass = 'success';
										break;
									case 'rejected':
										postObj.flagData.labelClass = 'danger';
										break;
								}
							}
 
							delete postObj[prop];
						}
					}
 
					if (postObj.flagData.assignee) {
						user.getUserFields(parseInt(postObj.flagData.assignee, 10), ['username', 'picture'], function (err, userData) {
							if (err) {
								return next(err);
							}
 
							postObj.flagData.assigneeUser = userData;
							next(null, postObj);
						});
					} else {
						setImmediate(next.bind(null, null, postObj));
					}
				}, next);
			}
		], callback);
	}
 
	Posts.updateFlagData = function (uid, pid, flagObj, callback) {
		// Retrieve existing flag data to compare for history-saving purposes
		var changes = [];
		var changeset = {};
		var prop;
 
		Posts.getPostData(pid, function (err, postData) {
			if (err) {
				return callback(err);
			}
 
			// Track new additions
			for(prop in flagObj) {
				if (flagObj.hasOwnProperty(prop) && !postData.hasOwnProperty('flag:' + prop) && flagObj[prop].length) {
					changes.push(prop);
				}
			}
 
			// Track changed items
			for(prop in postData) {
				if (
					postData.hasOwnProperty(prop) && prop.startsWith('flag:') &&
					flagObj.hasOwnProperty(prop.slice(5)) &&
					postData[prop] !== flagObj[prop.slice(5)]
				) {
					changes.push(prop.slice(5));
				}
			}
 
			changeset = changes.reduce(function (memo, prop) {
				memo['flag:' + prop] = flagObj[prop];
				return memo;
			}, {});
 
			// Append changes to history string
			if (changes.length) {
				try {
					var history = JSON.parse(postData['flag:history'] || '[]');
 
					changes.forEach(function (property) {
						switch(property) {
							case 'assignee':	// intentional fall-through
							case 'state':
								history.unshift({
									uid: uid,
									type: property,
									value: flagObj[property],
									timestamp: Date.now()
								});
								break;
 
							case 'notes':
								history.unshift({
									uid: uid,
									type: property,
									timestamp: Date.now()
								});
						}
					});
 
					changeset['flag:history'] = JSON.stringify(history);
				} catch (e) {
					winston.warn('[posts/updateFlagData] Unable to deserialise post flag history, likely malformed data');
				}
			}
 
			// Save flag data into post hash
			if (changes.length) {
				Posts.setPostFields(pid, changeset, callback);
			} else {
				setImmediate(callback);
			}
		});
	};
 
	Posts.expandFlagHistory = function (posts, callback) {
		// Expand flag history
		async.map(posts, function (post, next) {
			var history;
			try {
				history = JSON.parse(post['flag:history'] || '[]');
			} catch (e) {
				winston.warn('[posts/getFlags] Unable to deserialise post flag history, likely malformed data');
				return callback(e);
			}
 
			async.map(history, function (event, next) {
				event.timestampISO = new Date(event.timestamp).toISOString();
 
				async.parallel([
					function (next) {
						user.getUserFields(event.uid, ['username', 'picture'], function (err, userData) {
							if (err) {
								return next(err);
							}
 
							event.user = userData;
							next();
						});
					},
					function (next) {
						if (event.type === 'assignee') {
							user.getUserField(parseInt(event.value, 10), 'username', function (err, username) {
								if (err) {
									return next(err);
								}
 
								event.label = username || 'Unknown user';
								next(null);
							});
						} else if (event.type === 'state') {
							event.label = '[[topic:flag_manage_state_' + event.value + ']]';
							setImmediate(next);
						} else {
							setImmediate(next);
						}
					}
				], function (err) {
					next(err, event);
				});
			}, function (err, history) {
				if (err) {
					return next(err);
				}
 
				post['flag:history'] = history;
				next(null, post);
			});
		}, callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/parse.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/parse.js

Statements: 12.77% (6 / 47)      Branches: 0% (0 / 28)      Functions: 0% (0 / 6)      Lines: 12.77% (6 / 47)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94    2 2 2 2   2                                                                                                                                           1                                
'use strict';
 
var nconf = require('nconf');
var url = require('url');
var winston = require('winston');
var S = require('string');
 
var meta = require('../meta');
var cache = require('./cache');
var plugins = require('../plugins');
var translator = require('../../public/src/modules/translator');
 
var urlRegex = /href="([^"]+)"/g;
 
module.exports = function (Posts) {
 
	Posts.parsePost = function (postData, callback) {
		postData.content = postData.content || '';
 
		if (postData.pid && cache.has(String(postData.pid))) {
			postData.content = cache.get(String(postData.pid));
			return callback(null, postData);
		}
 
		// Casting post content into a string, just in case
		if (typeof postData.content !== 'string') {
			postData.content = postData.content.toString();
		}
 
		plugins.fireHook('filter:parse.post', {postData: postData}, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			data.postData.content = translator.escape(data.postData.content);
 
			if (global.env === 'production' && data.postData.pid) {
				cache.set(String(data.postData.pid), data.postData.content);
			}
 
			callback(null, data.postData);
		});
	};
 
	Posts.parseSignature = function (userData, uid, callback) {
		userData.signature = sanitizeSignature(userData.signature || '');
		plugins.fireHook('filter:parse.signature', {userData: userData, uid: uid}, callback);
	};
 
	Posts.relativeToAbsolute = function (content) {
		// Turns relative links in post body to absolute urls
		var parsed, current, absolute;
 
		while ((current = urlRegex.exec(content)) !== null) {
			if (current[1]) {
				try {
					parsed = url.parse(current[1]);
					if (!parsed.protocol) {
						if (current[1].startsWith('/')) {
							// Internal link
							absolute = nconf.get('url') + current[1];
						} else {
							// External link
							absolute = '//' + current[1];
						}
 
						content = content.slice(0, current.index + 6) + absolute + content.slice(current.index + 6 + current[1].length);
					}
				} catch(err) {
					winston.verbose(err.messsage);
				}
			}
		}
 
		return content;
	};
 
	function sanitizeSignature(signature) {
		var	string = S(signature),
			tagsToStrip = [];
 
		if (parseInt(meta.config['signatures:disableLinks'], 10) === 1) {
			tagsToStrip.push('a');
		}
 
		if (parseInt(meta.config['signatures:disableImages'], 10) === 1) {
			tagsToStrip.push('img');
		}
 
		return tagsToStrip.length ? string.stripTags.apply(string, tagsToStrip).s : signature;
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/recent.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/recent.js

Statements: 5% (1 / 20)      Branches: 0% (0 / 8)      Functions: 0% (0 / 11)      Lines: 5% (1 / 20)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56    2                                                                                                          
'use strict';
 
var async = require('async'),
	db = require('../database'),
	privileges = require('../privileges');
 
 
module.exports = function (Posts) {
	var terms = {
		day: 86400000,
		week: 604800000,
		month: 2592000000
	};
 
	Posts.getRecentPosts = function (uid, start, stop, term, callback) {
		var min = 0;
		if (terms[term]) {
			min = Date.now() - terms[term];
		}
 
		var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
 
		async.waterfall([
			function (next) {
				db.getSortedSetRevRangeByScore('posts:pid', start, count, '+inf', min, next);
			},
			function (pids, next) {
				privileges.posts.filter('read', pids, uid, next);
			},
			function (pids, next) {
				Posts.getPostSummaryByPids(pids, uid, {stripTags: true}, next);
			}
		], callback);
	};
 
	Posts.getRecentPosterUids = function (start, stop, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange('posts:pid', start, stop, next);
			},
			function (pids, next) {
				Posts.getPostsFields(pids, ['uid'], next);
			},
			function (postData, next) {
				postData = postData.map(function (post) {
					return post && post.uid;
				}).filter(function (value, index, array) {
					return value && array.indexOf(value) === index;
				});
				next(null, postData);
			}
		], callback);
 	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/summary.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/summary.js

Statements: 9.41% (8 / 85)      Branches: 0% (0 / 42)      Functions: 0% (0 / 22)      Lines: 9.41% (8 / 85)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154      2 2 2   2                                                                                                                                                                       1                                             1                                           1               1                  
 
'use strict';
 
var async = require('async');
var validator = require('validator');
var S = require('string');
 
var db = require('../database');
var user = require('../user');
var plugins = require('../plugins');
var categories = require('../categories');
var utils = require('../../public/src/utils');
 
 
module.exports = function (Posts) {
 
	Posts.getPostSummaryByPids = function (pids, uid, options, callback) {
		if (!Array.isArray(pids) || !pids.length) {
			return callback(null, []);
		}
 
		options.stripTags = options.hasOwnProperty('stripTags') ? options.stripTags : false;
		options.parse = options.hasOwnProperty('parse') ? options.parse : true;
		options.extraFields = options.hasOwnProperty('extraFields') ? options.extraFields : [];
 
		var fields = ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted', 'upvotes', 'downvotes'].concat(options.extraFields);
 
		var posts;
		async.waterfall([
			function (next) {
				Posts.getPostsFields(pids, fields, next);
			},
			function (_posts, next) {
				posts = _posts.filter(Boolean);
 
				var uids = [];
				var topicKeys = [];
 
				posts.forEach(function (post, i) {
					if (uids.indexOf(posts[i].uid) === -1) {
						uids.push(posts[i].uid);
					}
					if (topicKeys.indexOf('topic:' + posts[i].tid) === -1) {
						topicKeys.push('topic:' + posts[i].tid);
					}
				});
				async.parallel({
					users: function (next) {
						user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
					},
					topicsAndCategories: function (next) {
						getTopicAndCategories(topicKeys, next);
					}
				}, next);
			},
			function (results, next) {
				results.users = toObject('uid', results.users);
				results.topics = toObject('tid', results.topicsAndCategories.topics);
				results.categories = toObject('cid', results.topicsAndCategories.categories);
 
				posts.forEach(function (post) {
					// If the post author isn't represented in the retrieved users' data, then it means they were deleted, assume guest.
					if (!results.users.hasOwnProperty(post.uid)) {
						post.uid = 0;
					}
					post.user = results.users[post.uid];
					post.topic = results.topics[post.tid];
					post.category = results.categories[post.topic.cid];
					post.isMainPost = parseInt(post.pid, 10) === parseInt(post.topic.mainPid, 10);
					post.deleted = parseInt(post.deleted, 10) === 1;
					post.upvotes = parseInt(post.upvotes, 10) || 0;
					post.downvotes = parseInt(post.downvotes, 10) || 0;
					post.votes = post.upvotes - post.downvotes;
					post.timestampISO = utils.toISOString(post.timestamp);
				});
 
				posts = posts.filter(function (post) {
					return results.topics[post.tid];
				});
 
				parsePosts(posts, options, next);
			},
			function (posts, next) {
				plugins.fireHook('filter:post.getPostSummaryByPids', {posts: posts, uid: uid}, next);
			},
			function (data, next) {
				next(null, data.posts);
			}
		], callback);
	};
 
	function parsePosts(posts, options, callback) {
		async.map(posts, function (post, next) {
			if (!post.content || !options.parse) {
				if (options.stripTags) {
					post.content = stripTags(post.content);
				}
				post.content = post.content ? validator.escape(String(post.content)) : post.content;
				return next(null, post);
			}
 
			Posts.parsePost(post, function (err, post) {
				if (err) {
					return next(err);
				}
				if (options.stripTags) {
					post.content = stripTags(post.content);
				}
 
				next(null, post);
			});
		}, callback);
	}
 
	function getTopicAndCategories(topicKeys, callback) {
		db.getObjectsFields(topicKeys, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'postcount', 'mainPid'], function (err, topics) {
			if (err) {
				return callback(err);
			}
 
			var cids = topics.map(function (topic) {
				if (topic) {
					topic.title = validator.escape(String(topic.title));
					topic.deleted = parseInt(topic.deleted, 10) === 1;
				}
				return topic && topic.cid;
			}).filter(function (topic, index, array) {
				return topic && array.indexOf(topic) === index;
			});
 
			categories.getCategoriesFields(cids, ['cid', 'name', 'icon', 'slug', 'parentCid', 'bgColor', 'color'], function (err, categories) {
				callback(err, {topics: topics, categories: categories});
			});
		});
	}
 
	function toObject(key, data) {
		var obj = {};
		for(var i = 0; i < data.length; ++i) {
			obj[data[i][key]] = data[i];
		}
		return obj;
	}
 
	function stripTags(content) {
		if (content) {
			var s = S(content);
			return s.stripTags.apply(s, utils.stripTags).s;
		}
		return content;
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/tools.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/tools.js

Statements: 8.33% (3 / 36)      Branches: 0% (0 / 18)      Functions: 0% (0 / 12)      Lines: 8.33% (3 / 36)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77    2   2                           1                                                                                                                    
'use strict';
 
var async = require('async');
 
var privileges = require('../privileges');
var cache = require('./cache');
 
module.exports = function (Posts) {
	Posts.tools = {};
 
	Posts.tools.delete = function (uid, pid, callback) {
		togglePostDelete(uid, pid, true, callback);
	};
 
	Posts.tools.restore = function (uid, pid, callback) {
		togglePostDelete(uid, pid, false, callback);
	};
 
	function togglePostDelete(uid, pid, isDelete, callback) {
		async.waterfall([
			function (next) {
				Posts.exists(pid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-post]]'));
				}
				Posts.getPostField(pid, 'deleted', next);
			},
			function (deleted, next) {
				if (parseInt(deleted, 10) === 1 && isDelete) {
					return next(new Error('[[error:post-already-deleted]]'));
				} else if(parseInt(deleted, 10) !== 1 && !isDelete) {
					return next(new Error('[[error:post-already-restored]]'));
				}
 
				privileges.posts.canDelete(pid, uid, next);
			},
			function (canDelete, next) {
				if (!canDelete.flag) {
					return next(new Error(canDelete.message));
				}
 
				if (isDelete) {
					cache.del(pid);
					Posts.delete(pid, uid, next);
				} else {
					Posts.restore(pid, uid, function (err, postData) {
						if (err) {
							return next(err);
						}
						Posts.parsePost(postData, next);
					});
				}
			}
		], callback);
	}
 
	Posts.tools.purge = function (uid, pid, callback) {
		async.waterfall([
			function (next) {
				privileges.posts.canPurge(pid, uid, next);
			},
			function (canPurge, next) {
				if (!canPurge) {
					return next(new Error('[[error:no-privileges]]'));
				}
				cache.del(pid);
				Posts.purge(pid, uid, next);
			}
		], callback);
	};
 
};
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/topics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/topics.js

Statements: 5.71% (2 / 35)      Branches: 0% (0 / 14)      Functions: 0% (0 / 21)      Lines: 5.71% (2 / 35)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89      2   2                                                                                                                                                                      
 
'use strict';
 
var async = require('async');
 
var topics = require('../topics');
var utils = require('../../public/src/utils');
 
module.exports = function (Posts) {
 
	Posts.getPostsFromSet = function (set, start, stop, uid, reverse, callback) {
		async.waterfall([
			function (next) {
				Posts.getPidsFromSet(set, start, stop, reverse, next);
			},
			function (pids, next) {
				Posts.getPostsByPids(pids, uid, next);
			}
		], callback);
	};
 
	Posts.isMain = function (pid, callback) {
		async.waterfall([
			function (next) {
				Posts.getPostField(pid, 'tid', next);
			},
			function (tid, next) {
				topics.getTopicField(tid, 'mainPid', next);
			},
			function (mainPid, next) {
				next(null, parseInt(pid, 10) === parseInt(mainPid, 10));
			}
		], callback);
	};
 
	Posts.getTopicFields = function (pid, fields, callback) {
		async.waterfall([
			function (next) {
				Posts.getPostField(pid, 'tid', next);
			},
			function (tid, next) {
				topics.getTopicFields(tid, fields, next);
			}
		], callback);
	};
 
	Posts.generatePostPath = function (pid, uid, callback) {
		Posts.generatePostPaths([pid], uid, function (err, paths) {
			callback(err, Array.isArray(paths) && paths.length ? paths[0] : null);
		});
	};
 
	Posts.generatePostPaths = function (pids, uid, callback) {
		async.waterfall([
			function (next) {
				Posts.getPostsFields(pids, ['pid', 'tid'], next);
			},
			function (postData, next) {
				async.parallel({
					indices: function (next) {
						Posts.getPostIndices(postData, uid, next);
					},
					topics: function (next) {
						var tids = postData.map(function (post) {
							return post ? post.tid : null;
						});
 
						topics.getTopicsFields(tids, ['slug'], next);
					}
				}, next);
			},
			function (results, next) {
				var paths = pids.map(function (pid, index) {
					var slug = results.topics[index] ? results.topics[index].slug : null;
					var postIndex = utils.isNumber(results.indices[index]) ? parseInt(results.indices[index], 10) + 1 : null;
 
					if (slug && postIndex) {
						return '/topic/' + slug + '/' + postIndex;
					}
					return null;
				});
 
				next(null, paths);
			}
		], callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/user.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/user.js

Statements: 4.11% (3 / 73)      Branches: 0% (0 / 55)      Functions: 0% (0 / 22)      Lines: 4.23% (3 / 71)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131    2 2   2                                                                                                                                                                                                                                                          
'use strict';
 
var async = require('async');
var validator = require('validator');
 
var user = require('../user');
var groups = require('../groups');
var meta = require('../meta');
var plugins = require('../plugins');
 
module.exports = function (Posts) {
 
	Posts.getUserInfoForPosts = function (uids, uid, callback) {
		var groupsMap = {};
		var userData;
		async.waterfall([
			function (next) {
				user.getUsersFields(uids, ['uid', 'username', 'fullname', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned', 'status', 'lastonline', 'groupTitle'], next);
			},
			function (_userData, next) {
				userData = _userData;
				var groupTitles = userData.map(function (userData) {
					return userData && userData.groupTitle;
				}).filter(function (groupTitle, index, array) {
					return groupTitle && array.indexOf(groupTitle) === index;
				});
				groups.getGroupsData(groupTitles, next);
			}
		], function (err, groupsData) {
			if (err) {
				return callback(err);
			}
 
			groupsData.forEach(function (group) {
				if (group && group.userTitleEnabled) {
					groupsMap[group.name] = {
						name: group.name,
						slug: group.slug,
						labelColor: group.labelColor,
						icon: group.icon,
						userTitle: group.userTitle
					};
				}
			});
 
			userData.forEach(function (userData) {
				userData.uid = userData.uid || 0;
				userData.username = userData.username || '[[global:guest]]';
				userData.userslug = userData.userslug || '';
				userData.reputation = userData.reputation || 0;
				userData.postcount = userData.postcount || 0;
				userData.banned = parseInt(userData.banned, 10) === 1;
				userData.picture = userData.picture || '';
				userData.status = user.getStatus(userData);
				userData.signature = validator.escape(String(userData.signature || ''));
				userData.fullname = validator.escape(String(userData.fullname || ''));
			});
 
			async.map(userData, function (userData, next) {
				async.parallel({
					isMemberOfGroup: function (next) {
						if (!userData.groupTitle || !groupsMap[userData.groupTitle]) {
							return next();
						}
						groups.isMember(userData.uid, userData.groupTitle, next);
					},
					signature: function (next) {
						if (!userData.signature || parseInt(meta.config.disableSignatures, 10) === 1) {
							userData.signature = '';
							return next();
						}
						Posts.parseSignature(userData, uid, next);
					},
					customProfileInfo: function (next) {
						plugins.fireHook('filter:posts.custom_profile_info', {profile: [], uid: userData.uid}, next);
					}
				}, function (err, results) {
					if (err) {
						return next(err);
					}
 
					if (results.isMemberOfGroup && userData.groupTitle && groupsMap[userData.groupTitle]) {
						userData.selectedGroup = groupsMap[userData.groupTitle];
					}
 
					userData.custom_profile_info = results.customProfileInfo.profile;
 
					plugins.fireHook('filter:posts.modifyUserInfo', userData, next);
				});
			}, callback);
		});
	};
 
	Posts.isOwner = function (pid, uid, callback) {
		uid = parseInt(uid, 10);
		if (Array.isArray(pid)) {
			if (!uid) {
				return callback(null, pid.map(function () {return false;}));
			}
			Posts.getPostsFields(pid, ['uid'], function (err, posts) {
				if (err) {
					return callback(err);
				}
				posts = posts.map(function (post) {
					return post && parseInt(post.uid, 10) === uid;
				});
				callback(null, posts);
			});
		} else {
			if (!uid) {
				return callback(null, false);
			}
			Posts.getPostField(pid, 'uid', function (err, author) {
				callback(err, parseInt(author, 10) === uid);
			});
		}
	};
 
	Posts.isModerator = function (pids, uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, pids.map(function () {return false;}));
		}
		Posts.getCidsByPids(pids, function (err, cids) {
			if (err) {
				return callback(err);
			}
			user.isModerator(uid, cids, callback);
		});
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/votes.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/posts/votes.js

Statements: 6.72% (9 / 134)      Branches: 0% (0 / 85)      Functions: 0% (0 / 36)      Lines: 6.77% (9 / 133)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277    2   2                                                                                                                                                                                                               1       1         1                 1                   1                                                                                                       1                                                                                                 1                                                                              
'use strict';
 
var async = require('async');
 
var meta = require('../meta');
var db = require('../database');
var user = require('../user');
var plugins = require('../plugins');
 
module.exports = function (Posts) {
 
	var votesInProgress = {};
 
	Posts.upvote = function (pid, uid, callback) {
		if (parseInt(meta.config['reputation:disabled'], 10) === 1) {
			return callback(new Error('[[error:reputation-system-disabled]]'));
		}
 
		if (voteInProgress(pid, uid)) {
			return callback(new Error('[[error:already-voting-for-this-post]]'));
		}
 
		putVoteInProgress(pid, uid);
 
		toggleVote('upvote', pid, uid, function (err, data) {
			clearVoteProgress(pid, uid);
			callback(err, data);
		});
	};
 
	Posts.downvote = function (pid, uid, callback) {
		if (parseInt(meta.config['reputation:disabled'], 10) === 1) {
			return callback(new Error('[[error:reputation-system-disabled]]'));
		}
 
		if (parseInt(meta.config['downvote:disabled'], 10) === 1) {
			return callback(new Error('[[error:downvoting-disabled]]'));
		}
 
		if (voteInProgress(pid, uid)) {
			return callback(new Error('[[error:already-voting-for-this-post]]'));
		}
 
		putVoteInProgress(pid, uid);
 
		toggleVote('downvote', pid, uid, function (err, data) {
			clearVoteProgress(pid, uid);
			callback(err, data);
		});
	};
 
	Posts.unvote = function (pid, uid, callback) {
		if (voteInProgress(pid, uid)) {
			return callback(new Error('[[error:already-voting-for-this-post]]'));
		}
 
		putVoteInProgress(pid, uid);
 
		unvote(pid, uid, 'unvote', function (err, data) {
			clearVoteProgress(pid, uid);
			callback(err, data);
		});
	};
 
	Posts.hasVoted = function (pid, uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, {upvoted: false, downvoted: false});
		}
 
		db.isMemberOfSets(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], uid, function (err, hasVoted) {
			if (err) {
				return callback(err);
			}
 
			callback (null, {upvoted: hasVoted[0], downvoted: hasVoted[1]});
		});
	};
 
	Posts.getVoteStatusByPostIDs = function (pids, uid, callback) {
		if (!parseInt(uid, 10)) {
			var data = pids.map(function () { return false; });
			return callback(null, {upvotes: data, downvotes: data});
		}
		var upvoteSets = [];
		var downvoteSets = [];
 
		for (var i = 0; i < pids.length; ++i) {
			upvoteSets.push('pid:' + pids[i] + ':upvote');
			downvoteSets.push('pid:' + pids[i] + ':downvote');
		}
 
		async.parallel({
			upvotes: function (next) {
				db.isMemberOfSets(upvoteSets, uid, next);
			},
			downvotes: function (next) {
				db.isMemberOfSets(downvoteSets, uid, next);
			}
		}, callback);
	};
 
	Posts.getUpvotedUidsByPids = function (pids, callback) {
		var sets = pids.map(function (pid) {
			return 'pid:' + pid + ':upvote';
		});
		db.getSetsMembers(sets, callback);
	};
 
	function voteInProgress(pid, uid) {
		return Array.isArray(votesInProgress[uid]) && votesInProgress[uid].indexOf(parseInt(pid, 10)) !== -1;
	}
 
	function putVoteInProgress(pid, uid) {
		votesInProgress[uid] = votesInProgress[uid] || [];
		votesInProgress[uid].push(parseInt(pid, 10));
	}
 
	function clearVoteProgress(pid, uid) {
		if (Array.isArray(votesInProgress[uid])) {
			var index = votesInProgress[uid].indexOf(parseInt(pid, 10));
			if (index !== -1) {
				votesInProgress[uid].splice(index, 1);
			}
		}
	}
 
	function toggleVote(type, pid, uid, callback) {
		unvote(pid, uid, type, function (err) {
			if (err) {
				return callback(err);
			}
 
			vote(type, false, pid, uid, callback);
		});
	}
 
	function unvote(pid, uid, command, callback) {
		async.parallel({
			owner: function (next) {
				Posts.getPostField(pid, 'uid', next);
			},
			voteStatus: function (next) {
				Posts.hasVoted(pid, uid, next);
			},
			reputation: function (next) {
				user.getUserField(uid, 'reputation', next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (parseInt(uid, 10) === parseInt(results.owner, 10)) {
				return callback(new Error('self-vote'));
			}
 
			if (command === 'downvote' && parseInt(results.reputation) < parseInt(meta.config['privileges:downvote'], 10)) {
				return callback(new Error('[[error:not-enough-reputation-to-downvote]]'));
			}
 
			var voteStatus = results.voteStatus,
				hook,
				current = voteStatus.upvoted ? 'upvote' : 'downvote';
 
			if (voteStatus.upvoted && command === 'downvote' || voteStatus.downvoted && command === 'upvote') {	// e.g. User *has* upvoted, and clicks downvote
				hook = command;
			} else if (voteStatus.upvoted || voteStatus.downvoted) {	// e.g. User *has* upvoted, clicks upvote (so we "unvote")
				hook = 'unvote';
			} else {	// e.g. User *has not* voted, clicks upvote
				hook = command;
				current = 'unvote';
			}
 
			plugins.fireHook('action:post.' + hook, {
				pid: pid,
				uid: uid,
				owner: results.owner,
				current: current
			});
 
			if (!voteStatus || (!voteStatus.upvoted && !voteStatus.downvoted)) {
				return callback();
			}
 
			vote(voteStatus.upvoted ? 'downvote' : 'upvote', true, pid, uid, callback);
		});
	}
 
	function vote(type, unvote, pid, uid, callback) {
		uid = parseInt(uid, 10);
 
		if (uid === 0) {
			return callback(new Error('[[error:not-logged-in]]'));
		}
 
		Posts.getPostFields(pid, ['pid', 'uid', 'tid'], function (err, postData) {
			if (err) {
				return callback(err);
			}
 
			var now = Date.now();
 
			if (type === 'upvote' && !unvote) {
				db.sortedSetAdd('uid:' + uid + ':upvote', now, pid);
			} else {
				db.sortedSetRemove('uid:' + uid + ':upvote', pid);
			}
 
			if (type === 'upvote' || unvote) {
				db.sortedSetRemove('uid:' + uid + ':downvote', pid);
			} else {
				db.sortedSetAdd('uid:' + uid + ':downvote', now, pid);
			}
 
			user[type === 'upvote' ? 'incrementUserFieldBy' : 'decrementUserFieldBy'](postData.uid, 'reputation', 1, function (err, newreputation) {
				if (err) {
					return callback(err);
				}
 
				if (parseInt(postData.uid, 10)) {
					db.sortedSetAdd('users:reputation', newreputation, postData.uid);
				}
 
				adjustPostVotes(postData, uid, type, unvote, function (err) {
					callback(err, {
						user: {
							reputation: newreputation
						},
						post: postData,
						upvote: type === 'upvote' && !unvote,
						downvote: type === 'downvote' && !unvote
					});
				});
			});
		});
	}
 
	function adjustPostVotes(postData, uid, type, unvote, callback) {
		var notType = (type === 'upvote' ? 'downvote' : 'upvote');
 
		async.series([
			function (next) {
				if (unvote) {
					db.setRemove('pid:' + postData.pid + ':' + type, uid, next);
				} else {
					db.setAdd('pid:' + postData.pid + ':' + type, uid, next);
				}
			},
			function (next) {
				db.setRemove('pid:' + postData.pid + ':' + notType, uid, next);
			}
		], function (err) {
			if (err) {
				return callback(err);
			}
 
			async.parallel({
				upvotes: function (next) {
					db.setCount('pid:' + postData.pid + ':upvote', next);
				},
				downvotes: function (next) {
					db.setCount('pid:' + postData.pid + ':downvote', next);
				}
			}, function (err, results) {
				if (err) {
					return callback(err);
				}
				postData.upvotes = parseInt(results.upvotes, 10);
				postData.downvotes = parseInt(results.downvotes, 10);
				postData.votes = postData.upvotes - postData.downvotes;
				Posts.updatePostVoteCount(postData, callback);
			});
		});
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/

Statements: 4.32% (23 / 532)      Branches: 0% (0 / 300)      Functions: 0% (0 / 210)      Lines: 4.34% (23 / 530)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/privileges/
File Statements Branches Functions Lines
categories.js 2.41% (4 / 166) 0% (0 / 64) 0% (0 / 80) 2.41% (4 / 166)
helpers.js 8.96% (6 / 67) 0% (0 / 26) 0% (0 / 19) 8.96% (6 / 67)
posts.js 3.39% (4 / 118) 0% (0 / 87) 0% (0 / 42) 3.39% (4 / 118)
topics.js 2.86% (3 / 105) 0% (0 / 80) 0% (0 / 42) 2.86% (3 / 105)
users.js 7.89% (6 / 76) 0% (0 / 43) 0% (0 / 27) 8.11% (6 / 74)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/categories.js

Statements: 2.41% (4 / 166)      Branches: 0% (0 / 64)      Functions: 0% (0 / 80)      Lines: 2.41% (4 / 166)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407      10 10   10                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       1                                                                                                                                                                        
 
'use strict';
 
var async = require('async');
var _ = require('underscore');
 
var categories = require('../categories');
var user = require('../user');
var groups = require('../groups');
var helpers = require('./helpers');
var plugins = require('../plugins');
 
module.exports = function (privileges) {
 
	privileges.categories = {};
 
	privileges.categories.list = function (cid, callback) {
		// Method used in admin/category controller to show all users/groups with privs in that given cid
 
		var privilegeLabels = [
			{name: 'Find Category'},
			{name: 'Access Category'},
			{name: 'Access Topics'},
			{name: 'Create Topics'},
			{name: 'Reply to Topics'},
			{name: 'Edit Posts'},
			{name: 'Delete Posts'},
			{name: 'Delete Topics'},
			{name: 'Upload Images'},
			{name: 'Upload Files'},
			{name: 'Purge'},
			{name: 'Moderate'}
		];
 
		async.parallel({
			labels: function (next) {
				async.parallel({
					users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privilegeLabels),
					groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privilegeLabels)
				}, next);
			},
			users: function (next) {
				var userPrivileges;
				async.waterfall([
					async.apply(plugins.fireHook, 'filter:privileges.list', privileges.userPrivilegeList),
					function (_privs, next) {
						userPrivileges = _privs;
						groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
							return 'cid:' + cid + ':privileges:' + privilege;
						}), next);
					},
					function (memberSets, next) {
 
						memberSets = memberSets.map(function (set) {
							return set.map(function (uid) {
								return parseInt(uid, 10);
							});
						});
 
						var members = _.unique(_.flatten(memberSets));
 
						user.getUsersFields(members, ['picture', 'username'], function (err, memberData) {
							if (err) {
								return next(err);
							}
 
							memberData.forEach(function (member) {
								member.privileges = {};
								for(var x = 0,numPrivs = userPrivileges.length; x < numPrivs; x++) {
									member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
								}
							});
 
							next(null, memberData);
						});
					}
				], next);
			},
			groups: function (next) {
				var groupPrivileges;
				async.waterfall([
					async.apply(plugins.fireHook, 'filter:privileges.groups.list', privileges.groupPrivilegeList),
					function (_privs, next) {
						groupPrivileges = _privs;
						groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
							return 'cid:' + cid + ':privileges:' + privilege;
						}), next);
					},
					function (memberSets, next) {
 
						var uniqueGroups = _.unique(_.flatten(memberSets));
 
						groups.getGroups('groups:createtime', 0, -1, function (err, groupNames) {
							if (err) {
								return next(err);
							}
 
							groupNames = groupNames.filter(function (groupName) {
								return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
							});
 
							groupNames = groups.getEphemeralGroups().concat(groupNames);
							var registeredUsersIndex = groupNames.indexOf('registered-users');
							if (registeredUsersIndex !== -1) {
								groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
							} else {
								groupNames = ['registered-users'].concat(groupNames);
							}
 
							var adminIndex = groupNames.indexOf('administrators');
							if (adminIndex !== -1) {
								groupNames.splice(adminIndex, 1);
							}
 
							var memberPrivs;
 
							var memberData = groupNames.map(function (member) {
								memberPrivs = {};
 
								for(var x = 0,numPrivs = groupPrivileges.length; x < numPrivs; x++) {
									memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
								}
								return {
									name: member,
									privileges: memberPrivs,
								};
							});
 
							next(null, memberData);
						});
					},
					function (memberData, next) {
						// Grab privacy info for the groups as well
						async.map(memberData, function (member, next) {
							groups.isPrivate(member.name, function (err, isPrivate) {
								if (err) {
									return next(err);
								}
 
								member.isPrivate = isPrivate;
								next(null, member);
							});
						}, next);
					}
				], next);
			}
		}, function (err, payload) {
			if (err) {
				return callback(err);
			}
 
			// This is a hack because I can't do {labels.users.length} to echo the count in templates.js
			payload.columnCount = payload.labels.users.length + 2;
 
			callback(null, payload);
		});
	};
 
	privileges.categories.get = function (cid, uid, callback) {
		var privs = ['topics:create', 'topics:read', 'read'];
		async.parallel({
			privileges: function (next) {
				helpers.isUserAllowedTo(privs, uid, cid, next);
			},
			isAdministrator: function (next) {
				user.isAdministrator(uid, next);
			},
			isModerator: function (next) {
				user.isModerator(uid, cid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var privData = _.object(privs, results.privileges);
			var isAdminOrMod = results.isAdministrator || results.isModerator;
 
			plugins.fireHook('filter:privileges.categories.get', {
				'topics:create': privData['topics:create'] || isAdminOrMod,
				'topics:read': privData['topics:read'] || isAdminOrMod,
				read: privData.read || isAdminOrMod,
				cid: cid,
				uid: uid,
				editable: isAdminOrMod,
				view_deleted: isAdminOrMod,
				isAdminOrMod: isAdminOrMod
			}, callback);
		});
	};
 
	privileges.categories.isAdminOrMod = function (cid, uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, false);
		}
		helpers.some([
			function (next) {
				user.isModerator(uid, cid, next);
			},
			function (next) {
				user.isAdministrator(uid, next);
			}
		], callback);
	};
 
	privileges.categories.isUserAllowedTo = function (privilege, cid, uid, callback) {
		if (!cid) {
			return callback(null, false);
		}
		helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) {
			callback(err, Array.isArray(results) && results.length ? results[0] : false);
		});
	};
 
	privileges.categories.can = function (privilege, cid, uid, callback) {
		if (!cid) {
			return callback(null, false);
		}
 
		categories.getCategoryField(cid, 'disabled', function (err, disabled) {
			if (err) {
				return callback(err);
			}
 
			if (parseInt(disabled, 10) === 1) {
				return callback(null, false);
			}
 
			helpers.some([
				function (next) {
					helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) {
						next(err, Array.isArray(results) && results.length ? results[0] : false);
					});
				},
				function (next) {
					user.isModerator(uid, cid, next);
				},
				function (next) {
					user.isAdministrator(uid, next);
				}
			], callback);
		});
	};
 
	privileges.categories.filterCids = function (privilege, cids, uid, callback) {
		if (!Array.isArray(cids) || !cids.length) {
			return callback(null, []);
		}
 
		cids = cids.filter(function (cid, index, array) {
			return array.indexOf(cid) === index;
		});
 
		privileges.categories.getBase(privilege, cids, uid, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			cids = cids.filter(function (cid, index) {
				return !results.categories[index].disabled &&
					(results.allowedTo[index] || results.isAdmin || results.isModerators[index]);
			});
 
			callback(null, cids.filter(Boolean));
		});
	};
 
	privileges.categories.getBase = function (privilege, cids, uid, callback) {
		async.parallel({
			categories: function (next) {
				categories.getCategoriesFields(cids, ['disabled'], next);
			},
			allowedTo: function (next) {
				helpers.isUserAllowedTo(privilege, uid, cids, next);
			},
			isModerators: function (next) {
				user.isModerator(uid, cids, next);
			},
			isAdmin: function (next) {
				user.isAdministrator(uid, next);
			}
		}, callback);
	};
 
	privileges.categories.filterUids = function (privilege, cid, uids, callback) {
		if (!uids.length) {
			return callback(null, []);
		}
 
		uids = uids.filter(function (uid, index, array) {
			return array.indexOf(uid) === index;
		});
 
		async.parallel({
			allowedTo: function (next) {
				helpers.isUsersAllowedTo(privilege, uids, cid, next);
			},
			isModerators: function (next) {
				user.isModerator(uids, cid, next);
			},
			isAdmin: function (next) {
				user.isAdministrator(uids, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			uids = uids.filter(function (uid, index) {
				return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index];
			});
			callback(null, uids);
		});
	};
 
	privileges.categories.give = function (privileges, cid, groupName, callback) {
		giveOrRescind(groups.join, privileges, cid, groupName, callback);
	};
 
	privileges.categories.rescind = function (privileges, cid, groupName, callback) {
		giveOrRescind(groups.leave, privileges, cid, groupName, callback);
	};
 
	function giveOrRescind(method, privileges, cid, groupName, callback) {
		async.each(privileges, function (privilege, next) {
			method('cid:' + cid + ':privileges:groups:' + privilege, groupName, next);
		}, callback);
	}
 
	privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) {
		async.parallel({
			isAdministrator: function (next) {
				user.isAdministrator(uid, next);
			},
			moderatorOfCurrent: function (next) {
				user.isModerator(uid, currentCid, next);
			},
			moderatorOfTarget: function (next) {
				user.isModerator(uid, targetCid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			callback(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget));
		});
	};
 
	privileges.categories.userPrivileges = function (cid, uid, callback) {
		async.parallel({
			find: async.apply(groups.isMember, uid, 'cid:' + cid + ':privileges:find'),
			read: function (next) {
				groups.isMember(uid, 'cid:' + cid + ':privileges:read', next);
			},
			'topics:create': function (next) {
				groups.isMember(uid, 'cid:' + cid + ':privileges:topics:create', next);
			},
			'topics:read': function (next) {
				groups.isMember(uid, 'cid:' + cid + ':privileges:topics:read', next);
			},
			'topics:reply': function (next) {
				groups.isMember(uid, 'cid:' + cid + ':privileges:topics:reply', next);
			},
			'posts:edit': function (next) {
				groups.isMember(uid, 'cid:' + cid + ':privileges:posts:edit', next);
			},
			'posts:delete': function (next) {
				groups.isMember(uid, 'cid:' + cid + ':privileges:posts:delete', next);
			},
			'topics:delete': function (next) {
				groups.isMember(uid, 'cid:' + cid + ':privileges:topics:delete', next);
			},
			mods: function (next) {
				user.isModerator(uid, cid, next);
			}
		}, callback);
	};
 
	privileges.categories.groupPrivileges = function (cid, groupName, callback) {
		async.parallel({
			'groups:find': async.apply(groups.isMember, groupName, 'cid:' + cid + ':privileges:groups:find'),
			'groups:read': function (next) {
				groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:read', next);
			},
			'groups:topics:create': function (next) {
				groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:create', next);
			},
			'groups:topics:reply': function (next) {
				groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:reply', next);
			},
			'groups:posts:edit': function (next) {
				groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:posts:edit', next);
			},
			'groups:posts:delete': function (next) {
				groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:posts:delete', next);
			},
			'groups:topics:delete': function (next) {
				groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:delete', next);
			},
			'groups:topics:read': function (next) {
				groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:read', next);
			}
		}, callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/helpers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/helpers.js

Statements: 8.96% (6 / 67)      Branches: 0% (0 / 26)      Functions: 0% (0 / 19)      Lines: 8.96% (6 / 67)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135      2 2                                               1                                                               1                                                                                                             1                 1                    
 
'use strict';
 
var async = require('async');
var groups = require('../groups');
 
var helpers = {};
 
helpers.some = function (tasks, callback) {
	async.some(tasks, function (task, next) {
		task(function (err, result) {
			next(!err && result);
		});
	}, function (result) {
		callback(null, result);
	});
};
 
helpers.isUserAllowedTo = function (privilege, uid, cid, callback) {
	if (Array.isArray(privilege) && !Array.isArray(cid)) {
		isUserAllowedToPrivileges(privilege, uid, cid, callback);
	} else if (Array.isArray(cid) && !Array.isArray(privilege)) {
		isUserAllowedToCids(privilege, uid, cid, callback);
	} else {
		return callback(new Error('[[error:invalid-data]]'));
	}
};
 
function isUserAllowedToCids(privilege, uid, cids, callback) {
	if (parseInt(uid, 10) === 0) {
		return isGuestAllowedToCids(privilege, cids, callback);
	}
 
	var userKeys = [], groupKeys = [];
	for (var i = 0; i < cids.length; ++i) {
		userKeys.push('cid:' + cids[i] + ':privileges:' + privilege);
		groupKeys.push('cid:' + cids[i] + ':privileges:groups:' + privilege);
	}
 
	async.parallel({
		hasUserPrivilege: function (next) {
			groups.isMemberOfGroups(uid, userKeys, next);
		},
		hasGroupPrivilege: function (next) {
			groups.isMemberOfGroupsList(uid, groupKeys, next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		var result = [];
		for (var i = 0; i < cids.length; ++i) {
			result.push(results.hasUserPrivilege[i] || results.hasGroupPrivilege[i]);
		}
 
		callback(null, result);
	});
}
 
function isUserAllowedToPrivileges(privileges, uid, cid, callback) {
	if (parseInt(uid, 10) === 0) {
		return isGuestAllowedToPrivileges(privileges, cid, callback);
	}
 
	var userKeys = [], groupKeys = [];
	for (var i = 0; i < privileges.length; ++i) {
		userKeys.push('cid:' + cid + ':privileges:' + privileges[i]);
		groupKeys.push('cid:' + cid + ':privileges:groups:' + privileges[i]);
	}
 
	async.parallel({
		hasUserPrivilege: function (next) {
			groups.isMemberOfGroups(uid, userKeys, next);
		},
		hasGroupPrivilege: function (next) {
			groups.isMemberOfGroupsList(uid, groupKeys, next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		var result = [];
		for (var i = 0; i < privileges.length; ++i) {
			result.push(results.hasUserPrivilege[i] || results.hasGroupPrivilege[i]);
		}
 
		callback(null, result);
	});
}
 
 
helpers.isUsersAllowedTo = function (privilege, uids, cid, callback) {
	async.parallel({
		hasUserPrivilege: function (next) {
			groups.isMembers(uids, 'cid:' + cid + ':privileges:' + privilege, next);
		},
		hasGroupPrivilege: function (next) {
			groups.isMembersOfGroupList(uids, 'cid:' + cid + ':privileges:groups:' + privilege, next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		var result = [];
		for(var i = 0; i < uids.length; ++i) {
			result.push(results.hasUserPrivilege[i] || results.hasGroupPrivilege[i]);
		}
 
		callback(null, result);
	});
};
 
function isGuestAllowedToCids(privilege, cids, callback) {
	var groupKeys = [];
	for (var i = 0; i < cids.length; ++i) {
		groupKeys.push('cid:' + cids[i] + ':privileges:groups:' + privilege);
	}
 
	groups.isMemberOfGroups('guests', groupKeys, callback);
}
 
function isGuestAllowedToPrivileges(privileges, cid, callback) {
	var groupKeys = [];
	for (var i = 0; i < privileges.length; ++i) {
		groupKeys.push('cid:' + cid + ':privileges:groups:' + privileges[i]);
	}
 
	groups.isMemberOfGroups('guests', groupKeys, callback);
}
 
module.exports = helpers;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/posts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/posts.js

Statements: 3.39% (4 / 118)      Branches: 0% (0 / 87)      Functions: 0% (0 / 42)      Lines: 3.39% (4 / 118)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273      2   2                                                                                                                                                                                                                                                                                                                                                                                                                                                       1                                                           1                                  
 
'use strict';
 
var async = require('async');
 
var meta = require('../meta');
var posts = require('../posts');
var topics = require('../topics');
var user = require('../user');
var helpers = require('./helpers');
var plugins = require('../plugins');
 
module.exports = function (privileges) {
 
	privileges.posts = {};
 
	privileges.posts.get = function (pids, uid, callback) {
		if (!Array.isArray(pids) || !pids.length) {
			return callback(null, []);
		}
 
		async.waterfall([
			function (next) {
				posts.getCidsByPids(pids, next);
			},
			function (cids, next) {
				async.parallel({
					isAdmin: async.apply(user.isAdministrator, uid),
					isModerator: async.apply(user.isModerator, uid, cids),
					isOwner: async.apply(posts.isOwner, pids, uid),
					'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, cids),
					read: async.apply(helpers.isUserAllowedTo, 'read', uid, cids),
					'posts:edit': async.apply(helpers.isUserAllowedTo, 'posts:edit', uid, cids),
				}, next);
			}
		], function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var privileges = [];
 
			for (var i = 0; i < pids.length; ++i) {
				var isAdminOrMod = results.isAdmin || results.isModerator[i];
				var editable = isAdminOrMod || (results.isOwner[i] && results['posts:edit'][i]);
 
				privileges.push({
					editable: editable,
					view_deleted: editable,
					move: isAdminOrMod,
					isAdminOrMod: isAdminOrMod,
					'topics:read': results['topics:read'][i] || isAdminOrMod,
					read: results.read[i] || isAdminOrMod
				});
			}
 
			callback(null, privileges);
		});
	};
 
	privileges.posts.can = function (privilege, pid, uid, callback) {
		posts.getCidByPid(pid, function (err, cid) {
			if (err) {
				return callback(err);
			}
 
			privileges.categories.can(privilege, cid, uid, callback);
		});
	};
 
	privileges.posts.filter = function (privilege, pids, uid, callback) {
		if (!Array.isArray(pids) || !pids.length) {
			return callback(null, []);
		}
		var cids;
		var postData;
		var tids;
		var tidToTopic = {};
 
		async.waterfall([
			function (next) {
				posts.getPostsFields(pids, ['uid', 'tid', 'deleted'], next);
			},
			function (_posts, next) {
				postData = _posts;
				tids = _posts.map(function (post) {
					return post && post.tid;
				}).filter(function (tid, index, array) {
					return tid && array.indexOf(tid) === index;
				});
				topics.getTopicsFields(tids, ['deleted', 'cid'], next);
			},
			function (topicData, next) {
 
				topicData.forEach(function (topic, index) {
					if (topic) {
						tidToTopic[tids[index]] = topic;
					}
				});
 
				cids = postData.map(function (post, index) {
					if (post) {
						post.pid = pids[index];
						post.topic = tidToTopic[post.tid];
					}
					return tidToTopic[post.tid] && tidToTopic[post.tid].cid;
				}).filter(function (cid, index, array) {
					return cid && array.indexOf(cid) === index;
				});
 
				privileges.categories.getBase(privilege, cids, uid, next);
			},
			function (results, next) {
 
				var isModOf = {};
				cids = cids.filter(function (cid, index) {
					isModOf[cid] = results.isModerators[index];
					return !results.categories[index].disabled &&
						(results.allowedTo[index] || results.isAdmin || results.isModerators[index]);
				});
 
 
				pids = postData.filter(function (post) {
					return post.topic && cids.indexOf(post.topic.cid) !== -1 &&
						((parseInt(post.topic.deleted, 10) !== 1 && parseInt(post.deleted, 10) !== 1) || results.isAdmin || isModOf[post.cid]);
				}).map(function (post) {
					return post.pid;
				});
 
				plugins.fireHook('filter:privileges.posts.filter', {
					privilege: privilege,
					uid: uid,
					pids: pids
				}, function (err, data) {
					next(err, data ? data.pids : null);
				});
			}
		], callback);
	};
 
	privileges.posts.canEdit = function (pid, uid, callback) {
		async.parallel({
			isEditable: async.apply(isPostEditable, pid, uid),
			isAdminOrMod: async.apply(isAdminOrMod, pid, uid)
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			if (results.isAdminOrMod) {
				return callback(null, {flag: true});
			}
 
			callback(null, results.isEditable);
		});
	};
 
	privileges.posts.canDelete = function (pid, uid, callback) {
		var postData;
		async.waterfall([
			function (next) {
				posts.getPostFields(pid, ['uid', 'tid', 'timestamp', 'deleterUid'], next);
			},
			function (_postData, next) {
				postData = _postData;
				async.parallel({
					isAdminOrMod: async.apply(isAdminOrMod, pid, uid),
					isLocked: async.apply(topics.isLocked, postData.tid),
					isOwner: async.apply(posts.isOwner, pid, uid),
					'posts:delete': async.apply(privileges.posts.can, 'posts:delete', pid, uid)
				}, next);
			}
		], function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (results.isAdminOrMod) {
				return callback(null, {flag: true});
			}
 
			if (results.isLocked) {
				return callback(null, {flag: false, message: '[[error:topic-locked]]'});
			}
 
			if (!results['posts:delete']) {
				return callback(null, {flag: false, message: '[[error:no-privileges]]'});
			}
 
			var postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10);
			if (postDeleteDuration && (Date.now() - parseInt(postData.timestamp, 10) > postDeleteDuration * 1000)) {
				return callback(null, {flag: false, message: '[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]'});
			}
			var deleterUid = parseInt(postData.deleterUid, 10) || 0;
			var flag = results.isOwner && (deleterUid === 0 || deleterUid === parseInt(postData.uid, 10));
			callback(null, {flag: flag, message: '[[error:no-privileges]]'});
		});
	};
 
	privileges.posts.canMove = function (pid, uid, callback) {
		posts.isMain(pid, function (err, isMain) {
			if (err || isMain) {
				return callback(err || new Error('[[error:cant-move-mainpost]]'));
			}
			isAdminOrMod(pid, uid, callback);
		});
	};
 
	privileges.posts.canPurge = function (pid, uid, callback) {
		async.waterfall([
			function (next) {
				posts.getCidByPid(pid, next);
			},
			function (cid, next) {
				async.parallel({
					purge: async.apply(privileges.categories.isUserAllowedTo, 'purge', cid, uid),
					owner: async.apply(posts.isOwner, pid, uid),
					isAdminOrMod: async.apply(privileges.categories.isAdminOrMod, cid, uid)
				}, next);
			},
			function (results, next) {
				next(null, results.isAdminOrMod || (results.purge && results.owner));
			}
		], callback);
	};
 
	function isPostEditable(pid, uid, callback) {
		var tid;
		async.waterfall([
			function (next) {
				posts.getPostFields(pid, ['tid', 'timestamp'], next);
			},
			function (postData, next) {
				tid = postData.tid;
				var postEditDuration = parseInt(meta.config.postEditDuration, 10);
				if (postEditDuration && Date.now() - parseInt(postData.timestamp, 10) > postEditDuration * 1000) {
					return callback(null, {flag: false, message: '[[error:post-edit-duration-expired, ' + meta.config.postEditDuration + ']]'});
				}
				topics.isLocked(postData.tid, next);
			},
			function (isLocked, next) {
				if (isLocked) {
					return callback(null, {flag: false, message: '[[error:topic-locked]]'});
				}
 
				async.parallel({
					owner: async.apply(posts.isOwner, pid, uid),
					edit: async.apply(privileges.posts.can, 'posts:edit', pid, uid)
				}, next);
			},
			function (result, next) {
				next(null, {flag: result.owner && result.edit, message: '[[error:no-privileges]]'});
			}
		], callback);
	}
 
	function isAdminOrMod(pid, uid, callback) {
		helpers.some([
			function (next) {
				posts.getCidByPid(pid, function (err, cid) {
					if (err || !cid) {
						return next(err, false);
					}
 
					user.isModerator(uid, cid, next);
				});
			},
			function (next) {
				user.isAdministrator(uid, next);
			}
		], callback);
	}
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/topics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/topics.js

Statements: 2.86% (3 / 105)      Branches: 0% (0 / 80)      Functions: 0% (0 / 42)      Lines: 2.86% (3 / 105)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255      2 2   2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
 
'use strict';
 
var async = require('async');
var _ = require('underscore');
 
var meta = require('../meta');
var topics = require('../topics');
var user = require('../user');
var helpers = require('./helpers');
var categories = require('../categories');
var plugins = require('../plugins');
 
module.exports = function (privileges) {
 
	privileges.topics = {};
 
	privileges.topics.get = function (tid, uid, callback) {
		var topic;
		var privs = ['topics:reply', 'topics:read', 'topics:delete', 'posts:edit', 'posts:delete', 'read'];
		async.waterfall([
			async.apply(topics.getTopicFields, tid, ['cid', 'uid', 'locked', 'deleted']),
			function (_topic, next) {
				topic = _topic;
				async.parallel({
					privileges: async.apply(helpers.isUserAllowedTo, privs, uid, topic.cid),
					isAdministrator: async.apply(user.isAdministrator, uid),
					isModerator: async.apply(user.isModerator, uid, topic.cid),
					disabled: async.apply(categories.getCategoryField, topic.cid, 'disabled')
				}, next);
			}
		], function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var privData = _.object(privs, results.privileges);
			var disabled = parseInt(results.disabled, 10) === 1;
			var locked = parseInt(topic.locked, 10) === 1;
			var deleted = parseInt(topic.deleted, 10) === 1;
			var isOwner = !!parseInt(uid, 10) && parseInt(uid, 10) === parseInt(topic.uid, 10);
			var isAdminOrMod = results.isAdministrator || results.isModerator;
			var editable = isAdminOrMod;
			var deletable = isAdminOrMod || (isOwner && privData['topics:delete']);
 
			plugins.fireHook('filter:privileges.topics.get', {
				'topics:reply': (privData['topics:reply'] && !locked && !deleted) || isAdminOrMod,
				'topics:read': privData['topics:read'] || isAdminOrMod,
				'topics:delete': (isOwner && privData['topics:delete']) || isAdminOrMod,
				'posts:edit': (privData['posts:edit'] && !locked) || isAdminOrMod,
				'posts:delete': (privData['posts:delete'] && !locked) || isAdminOrMod,
				read: privData.read || isAdminOrMod,
				view_thread_tools: editable || deletable,
				editable: editable,
				deletable: deletable,
				view_deleted: isAdminOrMod || isOwner,
				isAdminOrMod: isAdminOrMod,
				disabled: disabled,
				tid: tid,
				uid: uid
			}, callback);
		});
	};
 
	privileges.topics.can = function (privilege, tid, uid, callback) {
		topics.getTopicField(tid, 'cid', function (err, cid) {
			if (err) {
				return callback(err);
			}
 
			privileges.categories.can(privilege, cid, uid, callback);
		});
	};
 
	privileges.topics.filterTids = function (privilege, tids, uid, callback) {
		if (!Array.isArray(tids) || !tids.length) {
			return callback(null, []);
		}
		var cids;
		var topicsData;
		async.waterfall([
			function (next) {
				topics.getTopicsFields(tids, ['tid', 'cid', 'deleted'], next);
			},
			function (_topicsData, next) {
				topicsData = _topicsData;
				cids = topicsData.map(function (topic) {
					return topic.cid;
				}).filter(function (cid, index, array) {
					return cid && array.indexOf(cid) === index;
				});
 
				privileges.categories.getBase(privilege, cids, uid, next);
			},
			function (results, next) {
 
				var isModOf = {};
				cids = cids.filter(function (cid, index) {
					isModOf[cid] = results.isModerators[index];
					return !results.categories[index].disabled &&
						(results.allowedTo[index] || results.isAdmin || results.isModerators[index]);
				});
 
				tids = topicsData.filter(function (topic) {
					return cids.indexOf(topic.cid) !== -1 &&
						(parseInt(topic.deleted, 10) !== 1 || results.isAdmin || isModOf[topic.cid]);
				}).map(function (topic) {
					return topic.tid;
				});
 
				plugins.fireHook('filter:privileges.topics.filter', {
					privilege: privilege,
					uid: uid,
					tids: tids
				}, function (err, data) {
					next(err, data ? data.tids : null);
				});
			}
		], callback);
	};
 
	privileges.topics.filterUids = function (privilege, tid, uids, callback) {
		if (!Array.isArray(uids) || !uids.length) {
			return callback(null, []);
		}
 
		uids = uids.filter(function (uid, index, array) {
			return array.indexOf(uid) === index;
		});
 
		async.waterfall([
			function (next) {
				topics.getTopicFields(tid, ['tid', 'cid', 'deleted'], next);
			},
			function (topicData, next) {
				async.parallel({
					disabled: function (next) {
						categories.getCategoryField(topicData.cid, 'disabled', next);
					},
					allowedTo: function (next) {
						helpers.isUsersAllowedTo(privilege, uids, topicData.cid, next);
					},
					isModerators: function (next) {
						user.isModerator(uids, topicData.cid, next);
					},
					isAdmins: function (next) {
						user.isAdministrator(uids, next);
					}
				}, function (err, results) {
					if (err) {
						return next(err);
					}
 
					uids = uids.filter(function (uid, index) {
						return parseInt(results.disabled, 10) !== 1 &&
							((results.allowedTo[index] && parseInt(topicData.deleted, 10) !== 1) || results.isAdmins[index] || results.isModerators[index]);
					});
 
					next(null, uids);
				});
			}
		], callback);
	};
 
	privileges.topics.canPurge = function (tid, uid, callback) {
		async.waterfall([
			function (next) {
				topics.getTopicField(tid, 'cid', next);
			},
			function (cid, next) {
				async.parallel({
					purge: async.apply(privileges.categories.isUserAllowedTo, 'purge', cid, uid),
					owner: async.apply(topics.isOwner, tid, uid),
					isAdminOrMod: async.apply(privileges.categories.isAdminOrMod, cid, uid)
				}, next);
			},
			function (results, next) {
				next(null, results.isAdminOrMod || (results.purge && results.owner));
			}
		], callback);
	};
 
	privileges.topics.canDelete = function (tid, uid, callback) {
		var topicData;
		async.waterfall([
			function (next) {
				topics.getTopicFields(tid, ['cid', 'postcount'], next);
			},
			function (_topicData, next) {
				topicData = _topicData;
				async.parallel({
					isModerator: async.apply(user.isModerator, uid, topicData.cid),
					isAdministrator: async.apply(user.isAdministrator, uid),
					isOwner: async.apply(topics.isOwner, tid, uid),
					'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [topicData.cid])
				}, next);
			}
		], function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (results.isModerator || results.isAdministrator) {
				return callback(null, true);
			}
 
			var preventTopicDeleteAfterReplies = parseInt(meta.config.preventTopicDeleteAfterReplies, 10) || 0;
			if (preventTopicDeleteAfterReplies && (topicData.postcount - 1) >= preventTopicDeleteAfterReplies) {
				var langKey = preventTopicDeleteAfterReplies > 1 ?
					'[[error:cant-delete-topic-has-replies, ' + meta.config.preventTopicDeleteAfterReplies + ']]' :
					'[[error:cant-delete-topic-has-reply]]';
				return callback(new Error(langKey));
			}
 
			if (!results['topics:delete'][0]) {
				return callback(null, false);
			}
 
			callback(null, results.isOwner);
		});
	};
 
	privileges.topics.canEdit = function (tid, uid, callback) {
		privileges.topics.isOwnerOrAdminOrMod(tid, uid, callback);
	};
 
	privileges.topics.isOwnerOrAdminOrMod = function (tid, uid, callback) {
		helpers.some([
			function (next) {
				topics.isOwner(tid, uid, next);
			},
			function (next) {
				privileges.topics.isAdminOrMod(tid, uid, next);
			}
		], callback);
	};
 
 
	privileges.topics.isAdminOrMod = function (tid, uid, callback) {
		helpers.some([
			function (next) {
				topics.getTopicField(tid, 'cid', function (err, cid) {
					if (err) {
						return next(err);
					}
					user.isModerator(uid, cid, next);
				});
			},
			function (next) {
				user.isAdministrator(uid, next);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/users.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/privileges/users.js

Statements: 7.89% (6 / 76)      Branches: 0% (0 / 43)      Functions: 0% (0 / 27)      Lines: 8.11% (6 / 74)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165      2   2                                                                     1                                                                                                       1                                   1                             1                                                                              
 
'use strict';
 
var async = require('async');
 
var groups = require('../groups');
var plugins = require('../plugins');
 
module.exports = function (privileges) {
 
	privileges.users = {};
 
	privileges.users.isAdministrator = function (uid, callback) {
		if (Array.isArray(uid)) {
			groups.isMembers(uid, 'administrators', callback);
		} else {
			groups.isMember(uid, 'administrators', callback);
		}
	};
 
	privileges.users.isGlobalModerator = function (uid, callback) {
		if (Array.isArray(uid)) {
			groups.isMembers(uid, 'Global Moderators', callback);
		} else {
			groups.isMember(uid, 'Global Moderators', callback);
		}
	};
 
	privileges.users.isModerator = function (uid, cid, callback) {
		if (Array.isArray(cid)) {
			isModeratorOfCategories(cid, uid, callback);
		} else {
			if (Array.isArray(uid)) {
				isModeratorsOfCategory(cid, uid, callback);
			} else {
				isModeratorOfCategory(cid, uid, callback);
			}
		}
	};
 
	function isModeratorOfCategories(cids, uid, callback) {
		if (!parseInt(uid, 10)) {
			return filterIsModerator(cids, uid, cids.map(function () {return false;}), callback);
		}
 
		privileges.users.isGlobalModerator(uid, function (err, isGlobalModerator) {
			if (err) {
				return callback(err);
			}
			if (isGlobalModerator) {
				return filterIsModerator(cids, uid, cids.map(function () {return true;}), callback);
			}
 
 
			var uniqueCids = cids.filter(function (cid, index, array) {
				return array.indexOf(cid) === index;
			});
 
			var groupNames = uniqueCids.map(function (cid) {
				return 'cid:' + cid + ':privileges:mods';	// At some point we should *probably* change this to "moderate" as well
			});
 
			var groupListNames = uniqueCids.map(function (cid) {
				return 'cid:' + cid + ':privileges:groups:moderate';
			});
 
			async.parallel({
				user: async.apply(groups.isMemberOfGroups, uid, groupNames),
				group: async.apply(groups.isMemberOfGroupsList, uid, groupListNames)
			}, function (err, checks) {
				if (err) {
					return callback(err);
				}
 
				var isMembers = checks.user.map(function (isMember, idx) {
						return isMember || checks.group[idx];
					}),
					map = {};
 
				uniqueCids.forEach(function (cid, index) {
					map[cid] = isMembers[index];
				});
 
				var isModerator = cids.map(function (cid) {
					return map[cid];
				});
 
				filterIsModerator(cids, uid, isModerator, callback);
			});
		});
	}
 
	function isModeratorsOfCategory(cid, uids, callback) {
		async.parallel([
			async.apply(privileges.users.isGlobalModerator, uids),
			async.apply(groups.isMembers, uids, 'cid:' + cid + ':privileges:mods'),
			async.apply(groups.isMembersOfGroupList, uids, 'cid:' + cid + ':privileges:groups:moderate')
		], function (err, checks) {
			if (err) {
				return callback(err);
			}
 
			var isModerator = checks[0].map(function (isMember, idx) {
				return isMember || checks[1][idx] || checks[2][idx];
			});
 
			filterIsModerator(cid, uids, isModerator, callback);
		});
	}
 
	function isModeratorOfCategory(cid, uid, callback) {
		async.parallel([
			async.apply(privileges.users.isGlobalModerator, uid),
			async.apply(groups.isMember, uid, 'cid:' + cid + ':privileges:mods'),
			async.apply(groups.isMemberOfGroupList, uid, 'cid:' + cid + ':privileges:groups:moderate')
		], function (err, checks) {
			if (err) {
				return callback(err);
			}
 
			var isModerator = checks[0] || checks[1] || checks[2];
			filterIsModerator(cid, uid, isModerator, callback);
		});
	}
 
	function filterIsModerator(cid, uid, isModerator, callback) {
		plugins.fireHook('filter:user.isModerator', {uid: uid, cid: cid, isModerator: isModerator}, function (err, data) {
			if (err) {
				return callback(err);
			}
			if (Array.isArray(uid) && !Array.isArray(data.isModerator) || Array.isArray(cid) && !Array.isArray(data.isModerator)) {
				return callback(new Error('filter:user.isModerator - i/o mismatch'));
			}
 
			callback(null, data.isModerator);
		});
	}
 
	privileges.users.canEdit = function (callerUid, uid, callback) {
		if (parseInt(callerUid, 10) === parseInt(uid, 10)) {
			return process.nextTick(callback, null, true);
		}
 
		async.parallel({
			isAdmin: function (next) {
				privileges.users.isAdministrator(callerUid, next);
			},
			isGlobalMod: function (next) {
				privileges.users.isGlobalModerator(callerUid, next);
			},
			isTargetAdmin: function (next) {
				privileges.users.isAdministrator(uid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var canEdit = results.isAdmin || (results.isGlobalMod && !results.isTargetAdmin);
 
			callback(null, canEdit);
		});
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/rewards/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/rewards/

Statements: 12% (15 / 125)      Branches: 0% (0 / 42)      Functions: 0% (0 / 55)      Lines: 12% (15 / 125)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/rewards/
File Statements Branches Functions Lines
admin.js 9.68% (6 / 62) 0% (0 / 16) 0% (0 / 27) 9.68% (6 / 62)
index.js 14.29% (9 / 63) 0% (0 / 26) 0% (0 / 28) 14.29% (9 / 63)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/rewards/admin.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/rewards/admin.js

Statements: 9.68% (6 / 62)      Branches: 0% (0 / 16)      Functions: 0% (0 / 27)      Lines: 9.68% (6 / 62)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144    2             1 1                                                                                                                                                     1                                             1     1                                                                
"use strict";
 
var rewards = {},
	async = require('async'),
	plugins = require('../plugins'),
	db = require('../database');
 
 
rewards.save = function (data, callback) {
	function save(data, next) {
		function commit(err, id) {
			if (err) {
				return callback(err);
			}
 
			data.id = id;
			
			async.series([
				function (next) {
					rewards.delete(data, next);
				},
				function (next) {
					db.setAdd('rewards:list', data.id, next);
				},
				function (next) {
					db.setObject('rewards:id:' + data.id, data, next);
				},
				function (next) {
					db.setObject('rewards:id:' + data.id + ':rewards', rewardsData, next);
				}
			], next);
		}
 
		if (!Object.keys(data.rewards).length) {
			return next();
		}
 
		var rewardsData = data.rewards;
		delete data.rewards;
 
		if (!parseInt(data.id, 10)) {
			db.incrObjectField('global', 'rewards:id', commit);
		} else {
			commit(false, data.id);
		}
	}
 
	async.each(data, save, function (err) {
		if (err) {
			return callback(err);
		}
 
		saveConditions(data, callback);
	});
};
 
rewards.delete = function (data, callback) {
	async.parallel([
		function (next) {
			db.setRemove('rewards:list', data.id, next);
		},
		function (next) {
			db.delete('rewards:id:' + data.id, next);
		},
		function (next) {
			db.delete('rewards:id:' + data.id + ':rewards', next);
		}
	], callback);
};
 
rewards.get = function (callback) {
	async.parallel({
		active: getActiveRewards,
		conditions: function (next) {
			plugins.fireHook('filter:rewards.conditions', [], next);
		},
		conditionals: function (next) {
			plugins.fireHook('filter:rewards.conditionals', [], next);
		},
		rewards: function (next) {
			plugins.fireHook('filter:rewards.rewards', [], next);
		}
	}, callback);
};
 
function saveConditions(data, callback) {
	db.delete('conditions:active', function (err) {
		if (err) {
			return callback(err);
		}
 
		var conditions = [],
			rewardsPerCondition = {};
 
		data.forEach(function (reward) {
			conditions.push(reward.condition);
			rewardsPerCondition[reward.condition] = rewardsPerCondition[reward.condition] || [];
			rewardsPerCondition[reward.condition].push(reward.id);
		});
 
		db.setAdd('conditions:active', conditions, callback);
 
		async.each(Object.keys(rewardsPerCondition), function (condition, next) {
			db.setAdd('condition:' + condition + ':rewards', rewardsPerCondition[condition], next);
		}, callback);
	});
}
 
function getActiveRewards(callback) {
	var activeRewards = [];
 
	function load(id, next) {
		async.parallel({
			main: function (next) {
				db.getObject('rewards:id:' + id, next);
			},
			rewards: function (next) {
				db.getObject('rewards:id:' + id + ':rewards', next);
			}
		}, function (err, data) {
			if (data.main) {
				data.main.disabled = data.main.disabled === 'true';
				data.main.rewards = data.rewards;
				activeRewards.push(data.main);
			}
 
			next(err);
		});
	}
 
	db.getSetMembers('rewards:list', function (err, rewards) {
		if (err) {
			return callback(err);
		}
 
		async.eachSeries(rewards, load, function (err) {
			callback(err, activeRewards);
		});
	});
}
 
module.exports = rewards;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/rewards/index.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/rewards/index.js

Statements: 14.29% (9 / 63)      Branches: 0% (0 / 26)      Functions: 0% (0 / 28)      Lines: 14.29% (9 / 63)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139    2                                                                                                       1             1       1       1                                                           1           1           1                       1                              
"use strict";
 
var rewards = {},
	db = require('../database'),
	plugins = require('../plugins'),
	async = require('async');
 
 
rewards.checkConditionAndRewardUser = function (uid, condition, method, callback) {
	async.waterfall([
		function (next) {
			isConditionActive(condition, function (err, isActive) {
				if (!isActive) {
					return back(err);
				}
 
				next(err);
			});
		},
		function (next) {
			getIDsByCondition(condition, function (err, ids) {
				next(err, ids);
			});
		},
		function (ids, next) {
			getRewardDataByIDs(ids, next);
		},
		function (rewards, next) {
			filterCompletedRewards(uid, rewards, function (err, filtered) {
				if (!filtered || !filtered.length) {
					return back(err);
				}
 
				next(err, filtered);
			});
		},
		function (rewards, next) {
			async.filter(rewards, function (reward, next) {
				if (!reward) {
					return next(false);
				}
 
				checkCondition(reward, method, next);
			}, function (eligible) {
				if (!eligible) {
					return next(false);
				}
 
				giveRewards(uid, eligible, next);
			});
		}
	], back);
 
 
	function back(err) {
		if (typeof callback === 'function') {
			callback(err);
		}
	}
};
 
function isConditionActive(condition, callback) {
	db.isSetMember('conditions:active', condition, callback);
}
 
function getIDsByCondition(condition, callback) {
	db.getSetMembers('condition:' + condition + ':rewards', callback);
}
 
function filterCompletedRewards(uid, rewards, callback) {
	db.getSortedSetRangeByScoreWithScores('uid:' + uid + ':rewards', 0, -1, 1, '+inf', function (err, data) {
		if (err) {
			return callback(err);
		}
 
		var userRewards = {};
 
		data.forEach(function (obj) {
			userRewards[obj.value] = parseInt(obj.score, 10);
		});
 
		rewards = rewards.filter(function (reward) {
			if (!reward) {
				return false;
			}
 
			var claimable = parseInt(reward.claimable, 10);
 
			if (claimable === 0) {
				return true;
			}
 
			return (userRewards[reward.id] >= reward.claimable) ? false : true;
		});
 
		callback(false, rewards);
	});
}
 
function getRewardDataByIDs(ids, callback) {
	db.getObjects(ids.map(function (id) {
		return 'rewards:id:' + id;
	}), callback);
}
 
function getRewardsByRewardData(rewards, callback) {
	db.getObjects(rewards.map(function (reward) {
		return 'rewards:id:' + reward.id + ':rewards';
	}), callback);
}
 
function checkCondition(reward, method, callback) {
	method(function (err, value) {
		if (err) {
			return callback(err);
		}
 
		plugins.fireHook('filter:rewards.checkConditional:' + reward.conditional, {left: value, right: reward.value}, function (err, bool) {
			callback(err || bool);
		});
	});
}
 
function giveRewards(uid, rewards, callback) {
	getRewardsByRewardData(rewards, function (err, rewardData) {
		if (err) {
			return callback(err);
		}
 
		async.each(rewards, function (reward, next) {
			plugins.fireHook('action:rewards.award:' + reward.rid, {uid: uid, reward: rewardData[rewards.indexOf(reward)]});
			db.sortedSetIncrBy('uid:' + uid + ':rewards', 1, reward.id, next);
		}, callback);
	});
}
 
 
module.exports = rewards;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/

Statements: 11.11% (59 / 531)      Branches: 0% (0 / 152)      Functions: 1.14% (1 / 88)      Lines: 11.11% (59 / 531)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/routes/
File Statements Branches Functions Lines
accounts.js 11.11% (3 / 27) 100% (0 / 0) 0% (0 / 1) 11.11% (3 / 27)
admin.js 8.96% (6 / 67) 100% (0 / 0) 0% (0 / 5) 8.96% (6 / 67)
api.js 7.41% (2 / 27) 100% (0 / 0) 0% (0 / 1) 7.41% (2 / 27)
authentication.js 5% (2 / 40) 0% (0 / 16) 11.11% (1 / 9) 5% (2 / 40)
debug.js 9.3% (4 / 43) 0% (0 / 18) 0% (0 / 10) 9.3% (4 / 43)
feeds.js 9.09% (15 / 165) 0% (0 / 90) 0% (0 / 40) 9.09% (15 / 165)
helpers.js 50% (3 / 6) 100% (0 / 0) 0% (0 / 1) 50% (3 / 6)
index.js 13.08% (14 / 107) 0% (0 / 10) 0% (0 / 12) 13.08% (14 / 107)
meta.js 23.33% (7 / 30) 0% (0 / 6) 0% (0 / 5) 23.33% (7 / 30)
plugins.js 15.79% (3 / 19) 0% (0 / 12) 0% (0 / 4) 15.79% (3 / 19)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/accounts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/accounts.js

Statements: 11.11% (3 / 27)      Branches: 100% (0 / 0)      Functions: 0% (0 / 1)      Lines: 11.11% (3 / 27)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38    1 1   1                                                                
"use strict";
 
var helpers = require('./helpers');
var setupPageRoute = helpers.setupPageRoute;
 
module.exports = function (app, middleware, controllers) {
	var middlewares = [middleware.checkGlobalPrivacySettings];
	var accountMiddlewares = [middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions];
 
	setupPageRoute(app, '/uid/:uid/:section1?/:section2?', middleware, [], middleware.redirectUidToUserslug);
 
	setupPageRoute(app, '/user/:userslug', middleware, middlewares, controllers.accounts.profile.get);
	setupPageRoute(app, '/user/:userslug/following', middleware, middlewares, controllers.accounts.follow.getFollowing);
	setupPageRoute(app, '/user/:userslug/followers', middleware, middlewares, controllers.accounts.follow.getFollowers);
	setupPageRoute(app, '/user/:userslug/posts', middleware, middlewares, controllers.accounts.posts.getPosts);
	setupPageRoute(app, '/user/:userslug/topics', middleware, middlewares, controllers.accounts.posts.getTopics);
	setupPageRoute(app, '/user/:userslug/best', middleware, middlewares, controllers.accounts.posts.getBestPosts);
	setupPageRoute(app, '/user/:userslug/groups', middleware, middlewares, controllers.accounts.groups.get);
 
	setupPageRoute(app, '/user/:userslug/bookmarks', middleware, accountMiddlewares, controllers.accounts.posts.getBookmarks);
	setupPageRoute(app, '/user/:userslug/watched', middleware, accountMiddlewares, controllers.accounts.posts.getWatchedTopics);
	setupPageRoute(app, '/user/:userslug/upvoted', middleware, accountMiddlewares, controllers.accounts.posts.getUpVotedPosts);
	setupPageRoute(app, '/user/:userslug/downvoted', middleware, accountMiddlewares, controllers.accounts.posts.getDownVotedPosts);
	setupPageRoute(app, '/user/:userslug/edit', middleware, accountMiddlewares, controllers.accounts.edit.get);
	setupPageRoute(app, '/user/:userslug/edit/username', middleware, accountMiddlewares, controllers.accounts.edit.username);
	setupPageRoute(app, '/user/:userslug/edit/email', middleware, accountMiddlewares, controllers.accounts.edit.email);
	setupPageRoute(app, '/user/:userslug/edit/password', middleware, accountMiddlewares, controllers.accounts.edit.password);
	setupPageRoute(app, '/user/:userslug/info', middleware, accountMiddlewares, controllers.accounts.info.get);
	setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.settings.get);
 
	app.delete('/api/user/:userslug/session/:uuid', [middleware.requireUser], controllers.accounts.session.revoke);
 
	setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get);
	setupPageRoute(app, '/user/:userslug/chats/:roomid?', middleware, middlewares, controllers.accounts.chats.get);
	setupPageRoute(app, '/chats/:roomid?', middleware, [middleware.authenticate], controllers.accounts.chats.redirectToChat);
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/admin.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/admin.js

Statements: 8.96% (6 / 67)      Branches: 100% (0 / 0)      Functions: 0% (0 / 5)      Lines: 8.96% (6 / 67)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101    1     1                                 1                   1                   1                                                                                                         1          
"use strict";
 
var express = require('express');
 
 
function apiRoutes(router, middleware, controllers) {
	router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
 
	var multipart = require('connect-multiparty');
	var multipartMiddleware = multipart();
 
	var middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.authenticate];
 
	router.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
	router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
	router.post('/uploadTouchIcon', middlewares, controllers.admin.uploads.uploadTouchIcon);
	router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
	router.post('/uploadOgImage', middlewares, controllers.admin.uploads.uploadOgImage);
	router.post('/upload/sound', middlewares, controllers.admin.uploads.uploadSound);
	router.post('/uploadDefaultAvatar', middlewares, controllers.admin.uploads.uploadDefaultAvatar);
}
 
function adminRouter(middleware, controllers) {
	var router = express.Router();
 
	router.use(middleware.admin.buildHeader);
 
	addRoutes(router, middleware, controllers);
 
	return router;
}
 
function apiRouter(middleware, controllers) {
	var router = express.Router();
 
	addRoutes(router, middleware, controllers);
 
	apiRoutes(router, middleware, controllers);
 
	return router;
}
 
function addRoutes(router, middleware, controllers) {
	var middlewares = [middleware.pluginHooks];
 
	router.get('/', middlewares, controllers.admin.dashboard.get);
	router.get('/general/dashboard', middlewares, controllers.admin.dashboard.get);
	router.get('/general/languages', middlewares, controllers.admin.languages.get);
	router.get('/general/sounds', middlewares, controllers.admin.sounds.get);
	router.get('/general/navigation', middlewares, controllers.admin.navigation.get);
	router.get('/general/homepage', middlewares, controllers.admin.homepage.get);
	router.get('/general/social', middlewares, controllers.admin.social.get);
 
	router.get('/manage/categories', middlewares, controllers.admin.categories.getAll);
	router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
	router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics);
 
	router.get('/manage/tags', middlewares, controllers.admin.tags.get);
	router.get('/manage/flags', middlewares, controllers.admin.flags.get);
	router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
 
	router.get('/manage/users', middlewares, controllers.admin.users.sortByJoinDate);
	router.get('/manage/users/search', middlewares, controllers.admin.users.search);
	router.get('/manage/users/latest', middlewares, controllers.admin.users.sortByJoinDate);
	router.get('/manage/users/not-validated', middlewares, controllers.admin.users.notValidated);
	router.get('/manage/users/no-posts', middlewares, controllers.admin.users.noPosts);
	router.get('/manage/users/top-posters', middlewares, controllers.admin.users.topPosters);
	router.get('/manage/users/most-reputation', middlewares, controllers.admin.users.mostReputaion);
	router.get('/manage/users/inactive', middlewares, controllers.admin.users.inactive);
	router.get('/manage/users/flagged', middlewares, controllers.admin.users.flagged);
	router.get('/manage/users/banned', middlewares, controllers.admin.users.banned);
	router.get('/manage/registration', middlewares, controllers.admin.users.registrationQueue);
 
	router.get('/manage/groups', middlewares, controllers.admin.groups.list);
	router.get('/manage/groups/:name', middlewares, controllers.admin.groups.get);
 
	router.get('/settings/:term?', middlewares, controllers.admin.settings.get);
 
	router.get('/appearance/:term?', middlewares, controllers.admin.appearance.get);
 
	router.get('/extend/plugins', middlewares, controllers.admin.plugins.get);
	router.get('/extend/widgets', middlewares, controllers.admin.extend.widgets.get);
	router.get('/extend/rewards', middlewares, controllers.admin.extend.rewards.get);
 
	router.get('/advanced/database', middlewares, controllers.admin.database.get);
	router.get('/advanced/events', middlewares, controllers.admin.events.get);
	router.get('/advanced/logs', middlewares, controllers.admin.logs.get);
	router.get('/advanced/errors', middlewares, controllers.admin.errors.get);
	router.get('/advanced/errors/export', middlewares, controllers.admin.errors.export);
	router.get('/advanced/cache', middlewares, controllers.admin.cache.get);
 
	router.get('/development/logger', middlewares, controllers.admin.logger.get);
	router.get('/development/info', middlewares, controllers.admin.info.get);
}
 
module.exports = function (app, middleware, controllers) {
	app.use('/admin/', adminRouter(middleware, controllers));
	app.use('/api/admin/', apiRouter(middleware, controllers));
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/api.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/api.js

Statements: 7.41% (2 / 27)      Branches: 100% (0 / 0)      Functions: 0% (0 / 1)      Lines: 7.41% (2 / 27)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42    2   2                                                                          
"use strict";
 
var express = require('express');
 
var uploadsController = require('../controllers/uploads');
 
module.exports =  function (app, middleware, controllers) {
 
	var router = express.Router();
	app.use('/api', router);
 
	router.get('/config', middleware.applyCSRF, controllers.api.getConfig);
	router.get('/widgets/render', controllers.api.renderWidgets);
 
	router.get('/me', middleware.checkGlobalPrivacySettings, controllers.api.getCurrentUser);
	router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUID);
	router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUsername);
	router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.api.getUserByEmail);
 
	router.get('/:type/pid/:id', controllers.api.getObject);
	router.get('/:type/tid/:id', controllers.api.getObject);
	router.get('/:type/cid/:id', controllers.api.getObject);
 
	router.get('/categories/:cid/moderators', controllers.api.getModerators);
	router.get('/recent/posts/:term?', controllers.api.getRecentPosts);
	router.get('/unread/:filter?/total', middleware.authenticate, controllers.unread.unreadTotal);
	router.get('/topic/teaser/:topic_id', controllers.topics.teaser);
	router.get('/topic/pagination/:topic_id', controllers.topics.pagination);
 
	var multipart = require('connect-multiparty');
	var multipartMiddleware = multipart();
	var middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF];
	router.post('/post/upload', middlewares, uploadsController.uploadPost);
	router.post('/topic/thumb/upload', middlewares, uploadsController.uploadThumb);
	router.post('/user/:userslug/uploadpicture', middlewares.concat([middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadPicture);
 
	router.post('/user/:userslug/uploadcover', middlewares.concat([middleware.authenticate, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]), controllers.accounts.edit.uploadCoverPicture);
	router.post('/groups/uploadpicture', middlewares.concat([middleware.authenticate]), controllers.groups.uploadCover);
};
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/authentication.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/authentication.js

Statements: 5% (2 / 40)      Branches: 0% (0 / 16)      Functions: 11.11% (1 / 9)      Lines: 5% (2 / 40)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 912     2                                                                                                                                                                              
(function (Auth) {
	"use strict";
 
	var passport = require('passport'),
		passportLocal = require('passport-local').Strategy,
		nconf = require('nconf'),
		winston = require('winston'),
		express = require('express'),
 
		controllers = require('../controllers'),
		plugins = require('../plugins'),
		hotswap = require('../hotswap'),
 
		loginStrategies = [];
 
	Auth.initialize = function (app, middleware) {
		app.use(passport.initialize());
		app.use(passport.session());
 
		app.use(function (req, res, next) {
			req.uid = req.user ? parseInt(req.user.uid, 10) : 0;
			next();
		});
 
		Auth.app = app;
		Auth.middleware = middleware;
	};
 
	Auth.getLoginStrategies = function () {
		return loginStrategies;
	};
 
	Auth.reloadRoutes = function (callback) {
		var router = express.Router();
		router.hotswapId = 'auth';
 
		loginStrategies.length = 0;
 
		if (plugins.hasListeners('action:auth.overrideLogin')) {
			winston.warn('[authentication] Login override detected, skipping local login strategy.');
			plugins.fireHook('action:auth.overrideLogin');
		} else {
			passport.use(new passportLocal({passReqToCallback: true}, controllers.authentication.localLogin));
		}
 
		plugins.fireHook('filter:auth.init', loginStrategies, function (err) {
			if (err) {
				winston.error('filter:auth.init - plugin failure');
				return callback(err);
			}
 
			loginStrategies.forEach(function (strategy) {
				if (strategy.url) {
					router.get(strategy.url, passport.authenticate(strategy.name, {
						scope: strategy.scope,
						prompt: strategy.prompt || undefined
					}));
				}
 
				router.get(strategy.callbackURL, passport.authenticate(strategy.name, {
					successReturnToOrRedirect: nconf.get('relative_path') + (strategy.successUrl !== undefined ? strategy.successUrl : '/'),
					failureRedirect: nconf.get('relative_path') + (strategy.failureUrl !== undefined ? strategy.failureUrl : '/login')
				}));
			});
 
			router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register);
			router.post('/register/complete', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.registerComplete);
			router.get('/register/abort', controllers.authentication.registerAbort);
			router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login);
			router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout);
 
			hotswap.replace('auth', router);
			if (typeof callback === 'function') {
				callback();
			}
		});
	};
 
	passport.serializeUser(function (user, done) {
		done(null, user.uid);
	});
 
	passport.deserializeUser(function (uid, done) {
		done(null, {
			uid: uid
		});
	});
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/debug.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/debug.js

Statements: 9.3% (4 / 43)      Branches: 0% (0 / 18)      Functions: 0% (0 / 10)      Lines: 9.3% (4 / 43)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84    2 2 2 2                                                                                                                                                            
"use strict";
 
var express = require('express');
var nconf = require('nconf');
var winston = require('winston');
var user = require('../user');
var categories = require('../categories');
var topics = require('../topics');
var posts = require('../posts');
var db = require('../database');
 
module.exports = function (app, middleware, controllers) {
	var router = express.Router();
 
	router.get('/uid/:uid', function (req, res) {
		if (!req.params.uid) {
			return res.redirect('/404');
		}
 
		user.getUserData(req.params.uid, function (err, data) {
			if (err) {
				winston.error(err);
			}
 
			if (data) {
				res.send(data);
			} else {
				res.status(404).json({
					error: "User doesn't exist!"
				});
			}
		});
	});
 
	router.get('/cid/:cid', function (req, res) {
		categories.getCategoryData(req.params.cid, function (err, data) {
			if (err) {
				winston.error(err);
			}
 
			if (data) {
				res.send(data);
			} else {
				res.status(404).send("Category doesn't exist!");
			}
		});
	});
 
	router.get('/tid/:tid', function (req, res) {
		topics.getTopicData(req.params.tid, function (err, data) {
			if (err) {
				winston.error(err);
			}
 
			if (data) {
				res.send(data);
			} else {
				res.status(404).send("Topic doesn't exist!");
			}
		});
	});
 
	router.get('/pid/:pid', function (req, res) {
		posts.getPostData(req.params.pid, function (err, data) {
			if (err) {
				winston.error(err);
			}
 
			if (data) {
				res.send(data);
			} else {
				res.status(404).send("Post doesn't exist!");
			}
		});
	});
 
	router.get('/test', function (req, res) {
		res.redirect(404);
	});
 
	app.use(nconf.get('relative_path') + '/debug', router);
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/feeds.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/feeds.js

Statements: 9.09% (15 / 165)      Branches: 0% (0 / 90)      Functions: 0% (0 / 40)      Lines: 9.09% (15 / 165)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378    2 2 2   2                 1                                                                                                                                                     1                                                               1                                                                                       1                         1                                                               1                             1                                                                                                     1                                         1                                                                             1                                               1                                
"use strict";
 
var async = require('async');
var rss = require('rss');
var nconf = require('nconf');
 
var posts = require('../posts');
var topics = require('../topics');
var user = require('../user');
var categories = require('../categories');
var meta = require('../meta');
var helpers = require('../controllers/helpers');
var privileges = require('../privileges');
 
 
function generateForTopic(req, res, callback) {
	if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
		return callback();
	}
 
	var tid = req.params.topic_id;
	var userPrivileges;
	async.waterfall([
		function (next) {
			async.parallel({
				privileges: function (next) {
					privileges.topics.get(tid, req.uid, next);
				},
				topic: function (next) {
					topics.getTopicData(tid, next);
				}
			}, next);
		},
		function (results, next) {
			if (!results.topic) {
				return callback();
			}
			if (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted) {
				return callback();
			}
			if (!results.privileges.read || !results.privileges['topics:read']) {
				return helpers.notAllowed(req, res);
			}
			userPrivileges = results.privileges;
			topics.getTopicWithPosts(results.topic, 'tid:' + tid + ':posts', req.uid, 0, 25, false, next);
		}
	], function (err, topicData) {
		if (err) {
			return callback(err);
		}
 
		topics.modifyPostsByPrivilege(topicData, userPrivileges);
 
		var description = topicData.posts.length ? topicData.posts[0].content : '';
		var image_url = topicData.posts.length ? topicData.posts[0].picture : '';
		var author = topicData.posts.length ? topicData.posts[0].username : '';
 
		var feed = new rss({
				title: topicData.title,
				description: description,
				feed_url: nconf.get('url') + '/topic/' + tid + '.rss',
				site_url: nconf.get('url') + '/topic/' + topicData.slug,
				image_url: image_url,
				author: author,
				ttl: 60
			}),
			dateStamp;
 
		if (topicData.posts.length > 0) {
			feed.pubDate = new Date(parseInt(topicData.posts[0].timestamp, 10)).toUTCString();
		}
 
		topicData.posts.forEach(function (postData) {
			if (!postData.deleted) {
				dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString();
 
				feed.item({
					title: 'Reply to ' + topicData.title + ' on ' + dateStamp,
					description: postData.content,
					url: nconf.get('url') + '/post/' + postData.pid,
					author: postData.user ? postData.user.username : '',
					date: dateStamp
				});
			}
		});
 
		sendFeed(feed, res);
	});
}
 
function generateForUserTopics(req, res, callback) {
	if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
		return callback();
	}
 
	var userslug = req.params.userslug;
 
	async.waterfall([
		function (next) {
			user.getUidByUserslug(userslug, next);
		},
		function (uid, next) {
			if (!uid) {
				return callback();
			}
			user.getUserFields(uid, ['uid', 'username'], next);
		}
	], function (err, userData) {
		if (err) {
			return callback(err);
		}
 
		generateForTopics({
			uid: req.uid,
			title: 'Topics by ' + userData.username,
			description: 'A list of topics that are posted by ' + userData.username,
			feed_url: '/user/' + userslug + '/topics.rss',
			site_url: '/user/' + userslug + '/topics'
		}, 'uid:' + userData.uid + ':topics', req, res, callback);
	});
}
 
function generateForCategory(req, res, next) {
	if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
		return next();
	}
	var cid = req.params.category_id;
 
	async.waterfall([
		function (next) {
			async.parallel({
				privileges: function (next) {
					privileges.categories.get(cid, req.uid, next);
				},
				category: function (next) {
					categories.getCategoryById({
						cid: cid,
						set: 'cid:' + cid + ':tids',
						reverse: true,
						start: 0,
						stop: 25,
						uid: req.uid
					}, next);
				}
			}, next);
		},
		function (results, next) {
			if (!results.privileges.read) {
				return helpers.notAllowed(req, res);
			}
			generateTopicsFeed({
				uid: req.uid,
				title: results.category.name,
				description: results.category.description,
				feed_url: '/category/' + cid + '.rss',
				site_url: '/category/' + results.category.cid,
			}, results.category.topics, next);
		}
	], function (err, feed) {
		if (err) {
			return next(err);
		}
		sendFeed(feed, res);
	});
}
 
function generateForRecent(req, res, next) {
	if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
		return next();
	}
	generateForTopics({
		uid: req.uid,
		title: 'Recently Active Topics',
		description: 'A list of topics that have been active within the past 24 hours',
		feed_url: '/recent.rss',
		site_url: '/recent'
	}, 'topics:recent', req, res, next);
}
 
function generateForPopular(req, res, next) {
	if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
		return next();
	}
	var terms = {
		daily: 'day',
		weekly: 'week',
		monthly: 'month',
		alltime: 'alltime'
	};
	var term = terms[req.params.term] || 'day';
 
	topics.getPopular(term, req.uid, 19, function (err, topics) {
		if (err) {
			return next(err);
		}
 
		generateTopicsFeed({
			uid: req.uid,
			title: 'Popular Topics',
			description: 'A list of topics that are sorted by post count',
			feed_url: '/popular/' + (req.params.term || 'daily') + '.rss',
			site_url: '/popular/' + (req.params.term || 'daily')
		}, topics, function (err, feed) {
			if (err) {
				return next(err);
			}
			sendFeed(feed, res);
		});
	});
}
 
function generateForTopics(options, set, req, res, next) {
	topics.getTopicsFromSet(set, req.uid, 0, 19, function (err, data) {
		if (err) {
			return next(err);
		}
 
		generateTopicsFeed(options, data.topics, function (err, feed) {
			if (err) {
				return next(err);
			}
			sendFeed(feed, res);
		});
	});
}
 
function generateTopicsFeed(feedOptions, feedTopics, callback) {
 
	feedOptions.ttl = 60;
	feedOptions.feed_url = nconf.get('url') + feedOptions.feed_url;
	feedOptions.site_url = nconf.get('url') + feedOptions.site_url;
 
	feedTopics = feedTopics.filter(Boolean);
 
	var	feed = new rss(feedOptions);
 
	if (feedTopics.length > 0) {
		feed.pubDate = new Date(parseInt(feedTopics[0].lastposttime, 10)).toUTCString();
	}
 
	async.map(feedTopics, function (topicData, next) {
		var feedItem = {
			title: topicData.title,
			url: nconf.get('url') + '/topic/' + topicData.slug,
			date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
		};
 
		if (topicData.teaser && topicData.teaser.user) {
			feedItem.description = topicData.teaser.content;
			feedItem.author = topicData.teaser.user.username;
			return next(null, feedItem);
		}
 
		topics.getMainPost(topicData.tid, feedOptions.uid, function (err, mainPost) {
			if (err) {
				return next(err);
			}
			if (!mainPost) {
				return next(null, feedItem);
			}
			feedItem.description = mainPost.content;
			feedItem.author = mainPost.user.username;
			next(null, feedItem);
		});
	}, function (err, feedItems) {
		if (err) {
			return callback(err);
		}
		feedItems.forEach(function (feedItem) {
			if (feedItem) {
				feed.item(feedItem);
			}
		});
		callback(null, feed);
	});
}
 
function generateForRecentPosts(req, res, next) {
	if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
		return next();
	}
 
	posts.getRecentPosts(req.uid, 0, 19, 'month', function (err, posts) {
		if (err) {
			return next(err);
		}
 
		var feed = generateForPostsFeed({
			title: 'Recent Posts',
			description: 'A list of recent posts',
			feed_url: '/recentposts.rss',
			site_url: '/recentposts'
		}, posts);
 
		sendFeed(feed, res);
	});
}
 
function generateForCategoryRecentPosts(req, res, next) {
	if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
		return next();
	}
	var cid = req.params.category_id;
 
	async.parallel({
		privileges: function (next) {
			privileges.categories.get(cid, req.uid, next);
		},
		category: function (next) {
			categories.getCategoryData(cid, next);
		},
		posts: function (next) {
			categories.getRecentReplies(cid, req.uid, 20, next);
		}
	}, function (err, results) {
		if (err) {
			return next(err);
		}
		if (!results.category) {
			return next();
		}
 
		if (!results.privileges.read) {
			return helpers.notAllowed(req, res);
		}
 
		var feed = generateForPostsFeed({
			title: results.category.name + ' Recent Posts',
			description: 'A list of recent posts from ' + results.category.name,
			feed_url: '/category/' + cid + '/recentposts.rss',
			site_url: '/category/' + cid + '/recentposts'
		}, results.posts);
 
		sendFeed(feed, res);
	});
}
 
function generateForPostsFeed(feedOptions, posts) {
	feedOptions.ttl = 60;
	feedOptions.feed_url = nconf.get('url') + feedOptions.feed_url;
	feedOptions.site_url = nconf.get('url') + feedOptions.site_url;
 
	var	feed = new rss(feedOptions);
 
	if (posts.length > 0) {
		feed.pubDate = new Date(parseInt(posts[0].timestamp, 10)).toUTCString();
	}
 
	posts.forEach(function (postData) {
		feed.item({
			title: postData.topic ? postData.topic.title : '',
			description: postData.content,
			url: nconf.get('url') + '/post/' + postData.pid,
			author: postData.user ? postData.user.username : '',
			date: new Date(parseInt(postData.timestamp, 10)).toUTCString()
		});
	});
 
	return feed;
}
 
function sendFeed(feed, res) {
	var xml = feed.xml();
	res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
}
 
module.exports = function (app, middleware, controllers) {
	app.get('/topic/:topic_id.rss', generateForTopic);
	app.get('/category/:category_id.rss', generateForCategory);
	app.get('/recent.rss', generateForRecent);
	app.get('/popular.rss', generateForPopular);
	app.get('/popular/:term.rss', generateForPopular);
	app.get('/recentposts.rss', generateForRecentPosts);
	app.get('/category/:category_id/recentposts.rss', generateForCategoryRecentPosts);
	app.get('/user/:userslug/topics.rss', generateForUserTopics);
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/helpers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/helpers.js

Statements: 50% (3 / 6)      Branches: 100% (0 / 0)      Functions: 0% (0 / 1)      Lines: 50% (3 / 6)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13    1   1             1  
'use strict';
 
var helpers = {};
 
helpers.setupPageRoute = function (router, name, middleware, middlewares, controller) {
	middlewares = middlewares.concat([middleware.registrationComplete, middleware.pageView, middleware.pluginHooks]);
 
	router.get(name, middleware.busyCheck, middleware.buildHeader, middlewares, controller);
	router.get('/api' + name, middlewares, controller);
};
 
module.exports = helpers;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/index.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/index.js

Statements: 13.08% (14 / 107)      Branches: 0% (0 / 10)      Functions: 0% (0 / 12)      Lines: 13.08% (14 / 107)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169    2 2 2 2 2                               1                                     1       1       1         1       1         1                   1           1                                                                                                                                                                                  
"use strict";
 
var nconf = require('nconf');
var winston = require('winston');
var path = require('path');
var async = require('async');
var controllers = require('../controllers');
var plugins = require('../plugins');
var user = require('../user');
var express = require('express');
 
var accountRoutes = require('./accounts');
var metaRoutes = require('./meta');
var apiRoutes = require('./api');
var adminRoutes = require('./admin');
var feedRoutes = require('./feeds');
var pluginRoutes = require('./plugins');
var authRoutes = require('./authentication');
var helpers = require('./helpers');
 
var setupPageRoute = helpers.setupPageRoute;
 
function mainRoutes(app, middleware, controllers) {
	setupPageRoute(app, '/', middleware, [], controllers.home);
 
	var loginRegisterMiddleware = [middleware.redirectToAccountIfLoggedIn];
 
	setupPageRoute(app, '/login', middleware, loginRegisterMiddleware, controllers.login);
	setupPageRoute(app, '/register', middleware, loginRegisterMiddleware, controllers.register);
	setupPageRoute(app, '/register/complete', middleware, [], controllers.registerInterstitial);
	setupPageRoute(app, '/compose', middleware, [], controllers.compose);
	setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail);
	setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing);
	setupPageRoute(app, '/search', middleware, [], controllers.search.search);
	setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset);
	setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse);
 
	app.get('/ping', controllers.ping);
	app.get('/sping', controllers.ping);
}
 
function modRoutes(app, middleware, controllers) {
	setupPageRoute(app, '/posts/flags', middleware, [], controllers.mods.flagged);
}
 
function globalModRoutes(app, middleware, controllers) {
	setupPageRoute(app, '/ip-blacklist', middleware, [], controllers.globalMods.ipBlacklist);
}
 
function topicRoutes(app, middleware, controllers) {
	setupPageRoute(app, '/topic/:topic_id/:slug/:post_index?', middleware, [], controllers.topics.get);
	setupPageRoute(app, '/topic/:topic_id/:slug?', middleware, [], controllers.topics.get);
}
 
function postRoutes(app, middleware, controllers) {
	setupPageRoute(app, '/post/:pid', middleware, [], controllers.posts.redirectToPost);
}
 
function tagRoutes(app, middleware, controllers) {
	setupPageRoute(app, '/tags/:tag', middleware, [middleware.privateTagListing], controllers.tags.getTag);
	setupPageRoute(app, '/tags', middleware, [middleware.privateTagListing], controllers.tags.getTags);
}
 
function categoryRoutes(app, middleware, controllers) {
	setupPageRoute(app, '/categories', middleware, [], controllers.categories.list);
	setupPageRoute(app, '/popular/:term?', middleware, [], controllers.popular.get);
	setupPageRoute(app, '/recent/:filter?', middleware, [], controllers.recent.get);
	setupPageRoute(app, '/unread/:filter?', middleware, [middleware.authenticate], controllers.unread.get);
 
	setupPageRoute(app, '/category/:category_id/:slug/:topic_index', middleware, [], controllers.category.get);
	setupPageRoute(app, '/category/:category_id/:slug?', middleware, [], controllers.category.get);
}
 
function userRoutes(app, middleware, controllers) {
	var middlewares = [middleware.checkGlobalPrivacySettings];
 
	setupPageRoute(app, '/users', middleware, middlewares, controllers.users.index);
}
 
function groupRoutes(app, middleware, controllers) {
	var middlewares = [middleware.checkGlobalPrivacySettings];
 
	setupPageRoute(app, '/groups', middleware, middlewares, controllers.groups.list);
	setupPageRoute(app, '/groups/:slug', middleware, middlewares, controllers.groups.details);
	setupPageRoute(app, '/groups/:slug/members', middleware, middlewares, controllers.groups.members);
}
 
module.exports = function (app, middleware, hotswapIds) {
	var routers = [
		express.Router(),	// plugin router
		express.Router(),	// main app router
		express.Router()	// auth router
	];
	var router = routers[1];
	var pluginRouter = routers[0];
	var authRouter = routers[2];
	var relativePath = nconf.get('relative_path');
	var ensureLoggedIn = require('connect-ensure-login');
 
	if (Array.isArray(hotswapIds) && hotswapIds.length) {
		for(var idx,x = 0; x < hotswapIds.length; x++) {
			idx = routers.push(express.Router()) - 1;
			routers[idx].hotswapId = hotswapIds[x];
		}
	}
 
	pluginRouter.render = function () {
		app.render.apply(app, arguments);
	};
 
	// Set-up for hotswapping (when NodeBB reloads)
	pluginRouter.hotswapId = 'plugins';
	authRouter.hotswapId = 'auth';
 
	app.all(relativePath + '(/api|/api/*?)', middleware.prepareAPI);
	app.all(relativePath + '(/api/admin|/api/admin/*?)', middleware.isAdmin);
	app.all(relativePath + '(/admin|/admin/*?)', ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login?local=1'), middleware.applyCSRF, middleware.isAdmin);
 
	app.use(middleware.maintenanceMode);
 
	adminRoutes(router, middleware, controllers);
	metaRoutes(router, middleware, controllers);
	apiRoutes(router, middleware, controllers);
	feedRoutes(router, middleware, controllers);
	pluginRoutes(router, middleware, controllers);
 
	mainRoutes(router, middleware, controllers);
	topicRoutes(router, middleware, controllers);
	postRoutes(router, middleware, controllers);
	modRoutes(router, middleware, controllers);
	globalModRoutes(router, middleware, controllers);
	tagRoutes(router, middleware, controllers);
	categoryRoutes(router, middleware, controllers);
	accountRoutes(router, middleware, controllers);
	userRoutes(router, middleware, controllers);
	groupRoutes(router, middleware, controllers);
 
	for(var x = 0; x < routers.length; x++) {
		app.use(relativePath, routers[x]);
	}
 
	if (process.env.NODE_ENV === 'development') {
		require('./debug')(app, middleware, controllers);
	}
 
	app.use(middleware.privateUploads);
	app.use(relativePath + '/api/language/:language/:namespace', middleware.getTranslation);
	app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), {
		maxAge: app.enabled('cache') ? 5184000000 : 0
	}));
	app.use('/vendor/jquery/timeago/locales', middleware.processTimeagoLocales);
	app.use(controllers.handle404);
	app.use(controllers.handleURIErrors);
	app.use(controllers.handleErrors);
 
	// Add plugin routes
	async.series([
		async.apply(plugins.reloadRoutes),
		async.apply(authRoutes.reloadRoutes),
		async.apply(user.addInterstitials)
	], function (err) {
		if (err) {
			return winston.error(err);
		}
		winston.info('Routes added');
	});
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/meta.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/meta.js

Statements: 23.33% (7 / 30)      Branches: 0% (0 / 6)      Functions: 0% (0 / 5)      Lines: 23.33% (7 / 30)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62    2 2   2     1                               1       1       1                                                          
"use strict";
 
var path = require('path');
var nconf = require('nconf');
 
var meta = require('../meta');
 
 
function sendMinifiedJS(req, res) {
	var target = path.basename(req.path);
	var cache = meta.js.target[target] ? meta.js.target[target].cache : '';
	res.type('text/javascript').send(cache);
}
 
// The portions of code involving the source map are commented out as they're broken in UglifyJS2
// Follow along here: https://github.com/mishoo/UglifyJS2/issues/700
// function sendJSSourceMap(req, res) {
// 	if (meta.js.hasOwnProperty('map')) {
// 		res.type('application/json').send(meta.js.map);
// 	} else {
// 		res.redirect(404);
// 	}
// };
 
function sendStylesheet(req, res) {
	res.type('text/css').status(200).send(meta.css.cache);
}
 
function sendACPStylesheet(req, res) {
	res.type('text/css').status(200).send(meta.css.acpCache);
}
 
function sendSoundFile(req, res, next) {
	var resolved = meta.sounds._filePathHash[path.basename(req.path)];
 
	if (resolved) {
		res.status(200).sendFile(resolved);
	} else {
		next();
	}
}
 
module.exports = function (app, middleware, controllers) {
	app.get('/stylesheet.css', middleware.addExpiresHeaders, sendStylesheet);
	app.get('/admin.css', middleware.addExpiresHeaders, sendACPStylesheet);
	app.get('/nodebb.min.js', middleware.addExpiresHeaders, sendMinifiedJS);
	app.get('/acp.min.js', middleware.addExpiresHeaders, sendMinifiedJS);
	// app.get('/nodebb.min.js.map', middleware.addExpiresHeaders, sendJSSourceMap);
	app.get('/sitemap.xml', controllers.sitemap.render);
	app.get('/sitemap/pages.xml', controllers.sitemap.getPages);
	app.get('/sitemap/categories.xml', controllers.sitemap.getCategories);
	app.get(/\/sitemap\/topics\.(\d+)\.xml/, controllers.sitemap.getTopicPage);
	app.get('/robots.txt', controllers.robots);
	app.get('/manifest.json', controllers.manifest);
	app.get('/css/previews/:theme', controllers.admin.themes.get);
 
	if (nconf.get('local-assets') === false) {
		app.get('/sounds/*', middleware.addExpiresHeaders, sendSoundFile);
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/plugins.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/routes/plugins.js

Statements: 15.79% (3 / 19)      Branches: 0% (0 / 12)      Functions: 0% (0 / 4)      Lines: 15.79% (3 / 19)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41    2 2   2                                                                      
"use strict";
 
var _ = require('underscore');
var path = require('path');
 
var plugins = require('../plugins');
 
module.exports = function (app, middleware, controllers) {
	// Static Assets
	app.get('/plugins/:id/*', middleware.addExpiresHeaders, function (req, res, next) {
 
		var relPath = req._parsedUrl.pathname.replace('/plugins/', '');
 
		var matches = _.map(plugins.staticDirs, function (realPath, mappedPath) {
			if (relPath.match(mappedPath)) {
				var pathToFile = path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
				if (pathToFile.startsWith(plugins.staticDirs[mappedPath])) {
					return pathToFile;
				}
			}
 
			return null;
		}).filter(Boolean);
 
		if (!matches || !matches.length) {
			return next();
		}
 
		res.sendFile(matches[0], {}, function (err) {
			if (err) {
				if (err.code === 'ENOENT') {
					// File doesn't exist, this isn't an error, to send to 404 handler
					return next();
				} else {
					return next(err);
				}
			}
		});
	});
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/

Statements: 4.68% (44 / 940)      Branches: 0% (0 / 536)      Functions: 0% (0 / 254)      Lines: 4.68% (44 / 940)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/socket.io/
File Statements Branches Functions Lines
blacklist.js 23.08% (3 / 13) 0% (0 / 6) 0% (0 / 3) 23.08% (3 / 13)
categories.js 2.8% (3 / 107) 0% (0 / 41) 0% (0 / 36) 2.8% (3 / 107)
groups.js 3.27% (5 / 153) 0% (0 / 103) 0% (0 / 47) 3.27% (5 / 153)
index.js 11.02% (13 / 118) 0% (0 / 77) 0% (0 / 24) 11.02% (13 / 118)
meta.js 5.71% (2 / 35) 0% (0 / 23) 0% (0 / 6) 5.71% (2 / 35)
modules.js 2.4% (4 / 167) 0% (0 / 114) 0% (0 / 50) 2.4% (4 / 167)
notifications.js 6.06% (2 / 33) 0% (0 / 16) 0% (0 / 8) 6.06% (2 / 33)
plugins.js 100% (2 / 2) 100% (0 / 0) 100% (0 / 0) 100% (2 / 2)
posts.js 3.7% (3 / 81) 0% (0 / 22) 0% (0 / 24) 3.7% (3 / 81)
topics.js 4.76% (3 / 63) 0% (0 / 30) 0% (0 / 14) 4.76% (3 / 63)
user.js 2.38% (4 / 168) 0% (0 / 104) 0% (0 / 42) 2.38% (4 / 168)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/blacklist.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/blacklist.js

Statements: 23.08% (3 / 13)      Branches: 0% (0 / 6)      Functions: 0% (0 / 3)      Lines: 23.08% (3 / 13)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29      2 2   2                                            
 
'use strict';
 
var async = require('async');
var winston = require('winston');
 
var user = require('../user');
var meta = require('../meta');
 
var SocketBlacklist = {};
 
SocketBlacklist.validate = function (socket, data, callback) {
	meta.blacklist.validate(data.rules, callback);
};
 
SocketBlacklist.save = function (socket, rules, callback) {
	user.isAdminOrGlobalMod(socket.uid, function (err, isAdminOrGlobalMod) {
		if (err || !isAdminOrGlobalMod) {
			return callback(err || new Error('[[error:no-privileges]]'));
		}
 
		meta.blacklist.save(rules, callback);
	});
};
 
 
module.exports = SocketBlacklist;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/categories.js

Statements: 2.8% (3 / 107)      Branches: 0% (0 / 41)      Functions: 0% (0 / 36)      Lines: 2.8% (3 / 107)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238    2 2                                                                                                                                                                                                                                                                                                                                                                                 1                                                                                                  
'use strict';
 
var async = require('async');
var db = require('../database');
var categories = require('../categories');
var privileges = require('../privileges');
var user = require('../user');
var topics = require('../topics');
var apiController = require('../controllers/api');
 
var SocketCategories = {};
 
SocketCategories.getRecentReplies = function (socket, cid, callback) {
	categories.getRecentReplies(cid, socket.uid, 4, callback);
};
 
SocketCategories.get = function (socket, data, callback) {
	async.parallel({
		isAdmin: async.apply(user.isAdministrator, socket.uid),
		categories: function (next) {
			async.waterfall([
				async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
				async.apply(categories.getCategoriesData),
			], next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		results.categories = results.categories.filter(function (category) {
			return category && (!category.disabled || results.isAdmin);
		});
 
		callback(null, results.categories);
	});
};
 
SocketCategories.getWatchedCategories = function (socket, data, callback) {
	async.parallel({
		categories: async.apply(categories.getCategoriesByPrivilege, 'cid:0:children', socket.uid, 'find'),
		ignoredCids: async.apply(user.getIgnoredCategories, socket.uid)
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
		var watchedCategories =  results.categories.filter(function (category) {
			return category && results.ignoredCids.indexOf(category.cid.toString()) === -1;
		});
 
		callback(null, watchedCategories);
	});
};
 
SocketCategories.loadMore = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.parallel({
		privileges: function (next) {
			privileges.categories.get(data.cid, socket.uid, next);
		},
		settings: function (next) {
			user.getSettings(socket.uid, next);
		},
		targetUid: function (next) {
			if (data.author) {
				user.getUidByUserslug(data.author, next);
			} else {
				next();
			}
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		if (!results.privileges.read) {
			return callback(new Error('[[error:no-privileges]]'));
		}
 
		var infScrollTopicsPerPage = 20;
		var set = 'cid:' + data.cid + ':tids';
		var reverse = false;
 
		if (data.categoryTopicSort === 'newest_to_oldest') {
			reverse = true;
		} else if (data.categoryTopicSort === 'most_posts') {
			reverse = true;
			set = 'cid:' + data.cid + ':tids:posts';
		}
 
		var start = Math.max(0, parseInt(data.after, 10));
 
		if (data.direction === -1) {
			start = start - (reverse ? infScrollTopicsPerPage : -infScrollTopicsPerPage);
		}
 
		var stop = start + infScrollTopicsPerPage - 1;
 
		start = Math.max(0, start);
		stop = Math.max(0, stop);
 
		if (results.targetUid) {
			set = 'cid:' + data.cid + ':uid:' + results.targetUid + ':tids';
		}
 
		if (data.tag) {
			set = [set, 'tag:' + data.tag + ':topics'];
		}
 
		categories.getCategoryTopics({
			cid: data.cid,
			set: set,
			reverse: reverse,
			start: start,
			stop: stop,
			uid: socket.uid,
			targetUid: results.targetUid,
			settings: results.settings
		}, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			categories.modifyTopicsByPrivilege(data.topics, results.privileges);
 
			data.privileges = results.privileges;
			data.template = {
				category: true,
				name: 'category'
			};
 
			callback(null, data);
		});
	});
};
 
SocketCategories.getPageCount = function (socket, cid, callback) {
	categories.getPageCount(cid, socket.uid, callback);
};
 
SocketCategories.getTopicCount = function (socket, cid, callback) {
	categories.getCategoryField(cid, 'topic_count', callback);
};
 
SocketCategories.getCategoriesByPrivilege = function (socket, privilege, callback) {
	categories.getCategoriesByPrivilege('categories:cid', socket.uid, privilege, callback);
};
 
SocketCategories.getMoveCategories = function (socket, data, callback) {
	async.parallel({
		isAdmin: async.apply(user.isAdministrator, socket.uid),
		categories: function (next) {
			async.waterfall([
				function (next) {
					db.getSortedSetRange('cid:0:children', 0, -1, next);
				},
				function (cids, next) {
					privileges.categories.filterCids('read', cids, socket.uid, next);
				},
				function (cids, next) {
					categories.getCategories(cids, socket.uid, next);
				}
			], next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		results.categories = results.categories.filter(function (category) {
			return category && (!category.disabled || results.isAdmin) && !category.link;
		});
 
		callback(null, results.categories);
	});
};
 
SocketCategories.watch = function (socket, cid, callback) {
	ignoreOrWatch(user.watchCategory, socket, cid, callback);
};
 
SocketCategories.ignore = function (socket, cid, callback) {
	ignoreOrWatch(user.ignoreCategory, socket, cid, callback);
};
 
function ignoreOrWatch(fn, socket, cid, callback) {
	async.waterfall([
		function (next) {
			db.getSortedSetRange('categories:cid', 0, -1, next);
		},
		function (cids, next) {
			categories.getCategoriesFields(cids, ['cid', 'parentCid'], next);
		},
		function (categoryData, next) {
			categoryData.forEach(function (c) {
				c.cid = parseInt(c.cid, 10);
				c.parentCid = parseInt(c.parentCid, 10);
			});
 
			var cids = [parseInt(cid, 10)];
 
			// filter to subcategories of cid
 
			var any = true;
			while (any) {
				any = false;
				categoryData.forEach(function (c) {
					if (cids.indexOf(c.cid) === -1 && cids.indexOf(c.parentCid) !== -1) {
						cids.push(c.cid);
						any = true;
					}
				});
			}
 
			async.each(cids, function (cid, next) {
				fn(socket.uid, cid, next);
			}, next);
		},
		function (next) {
			topics.pushUnreadCount(socket.uid, next);
		}
	], callback);
}
 
SocketCategories.isModerator = function (socket, cid, callback) {
	user.isModerator(socket.uid, cid, callback);
};
 
SocketCategories.getCategory = function (socket, cid, callback) {
	apiController.getCategoryData(cid, socket.uid, callback);
};
 
module.exports = SocketCategories;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/groups.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/groups.js

Statements: 3.27% (5 / 153)      Branches: 0% (0 / 103)      Functions: 0% (0 / 47)      Lines: 3.27% (5 / 153)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305    2   2                                                                                                                                         1                           1                                                                     1                                                                                                                                                                                                                                                                                                                                                                            
"use strict";
 
var	async = require('async');
 
var groups = require('../groups');
var meta = require('../meta');
var user = require('../user');
var utils = require('../../public/src/utils');
var groupsController = require('../controllers/groups');
 
var SocketGroups = {};
 
 
SocketGroups.before = function (socket, method, data, next) {
	if (!data) {
		return next(new Error('[[error:invalid-data]]'));
	}
	next();
};
 
SocketGroups.join = function (socket, data, callback) {
	if (!parseInt(socket.uid, 10)) {
		return callback(new Error('[[error:invalid-uid]]'));
	}
 
	if (data.groupName === 'administrators' || groups.isPrivilegeGroup(data.groupName)) {
		return callback(new Error('[[error:not-allowed]]'));
	}
 
	async.waterfall([
		function (next) {
			groups.exists(data.groupName, next);
		},
		function (exists, next) {
			if (!exists) {
				return next(new Error('[[error:no-group]]'));
			}
 
			if (parseInt(meta.config.allowPrivateGroups, 10) !== 1) {
				return groups.join(data.groupName, socket.uid, callback);
			}
 
			async.parallel({
				isAdmin: async.apply(user.isAdministrator, socket.uid),
				groupData: async.apply(groups.getGroupData, data.groupName)
			}, next);
		},
		function (results, next) {
			if (results.groupData.private && results.groupData.disableJoinRequests) {
				return next(new Error('[[error:join-requests-disabled]]'));
			}
 
			if (!results.groupData.private || results.isAdmin) {
				groups.join(data.groupName, socket.uid, next);
			} else {
				groups.requestMembership(data.groupName, socket.uid, next);
			}
		}
	], callback);
};
 
SocketGroups.leave = function (socket, data, callback) {
	if (!parseInt(socket.uid, 10)) {
		return callback(new Error('[[error:invalid-uid]]'));
	}
 
	if (data.groupName === 'administrators') {
		return callback(new Error('[[error:cant-remove-self-as-admin]]'));
	}
 
	groups.leave(data.groupName, socket.uid, callback);
};
 
function isOwner(next) {
	return function (socket, data, callback) {
		async.parallel({
			isAdmin: async.apply(user.isAdministrator, socket.uid),
			isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName)
		}, function (err, results) {
			if (err || (!isOwner && !results.isAdmin)) {
				return callback(err || new Error('[[error:no-privileges]]'));
			}
			next(socket, data, callback);
		});
	};
}
 
function isInvited(next) {
	return function (socket, data, callback) {
		groups.isInvited(socket.uid, data.groupName, function (err, invited) {
			if (err || !invited) {
				return callback(err || new Error('[[error:not-invited]]'));
			}
			next(socket, data, callback);
		});
	};
}
 
SocketGroups.grant = isOwner(function (socket, data, callback) {
	groups.ownership.grant(data.toUid, data.groupName, callback);
});
 
SocketGroups.rescind = isOwner(function (socket, data, callback) {
	groups.ownership.rescind(data.toUid, data.groupName, callback);
});
 
SocketGroups.accept = isOwner(function (socket, data, callback) {
	groups.acceptMembership(data.groupName, data.toUid, callback);
});
 
SocketGroups.reject = isOwner(function (socket, data, callback) {
	groups.rejectMembership(data.groupName, data.toUid, callback);
});
 
SocketGroups.acceptAll = isOwner(function (socket, data, callback) {
	acceptRejectAll(groups.acceptMembership, socket, data, callback);
});
 
SocketGroups.rejectAll = isOwner(function (socket, data, callback) {
	acceptRejectAll(groups.rejectMembership, socket, data, callback);
});
 
function acceptRejectAll(method, socket, data, callback) {
	async.waterfall([
		function (next) {
			groups.getPending(data.groupName, next);
		},
		function (uids, next) {
			async.each(uids, function (uid, next) {
				method(data.groupName, uid, next);
			}, next);
		}
	], callback);
}
 
SocketGroups.issueInvite = isOwner(function (socket, data, callback) {
	groups.invite(data.groupName, data.toUid, callback);
});
 
SocketGroups.issueMassInvite = isOwner(function (socket, data, callback) {
	if (!data || !data.usernames || !data.groupName) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	var usernames = data.usernames.split(',');
	usernames = usernames.map(function (username) {
		return username && username.trim();
	});
	user.getUidsByUsernames(usernames, function (err, uids) {
		if (err) {
			return callback(err);
		}
		uids = uids.filter(function (uid) {
			return !!uid && parseInt(uid, 10);
		});
 
		async.eachSeries(uids, function (uid, next) {
			groups.invite(data.groupName, uid, next);
		}, callback);
	});
});
 
SocketGroups.rescindInvite = isOwner(function (socket, data, callback) {
	groups.rejectMembership(data.groupName, data.toUid, callback);
});
 
SocketGroups.acceptInvite = isInvited(function (socket, data, callback) {
	groups.acceptMembership(data.groupName, socket.uid, callback);
});
 
SocketGroups.rejectInvite = isInvited(function (socket, data, callback) {
	groups.rejectMembership(data.groupName, socket.uid, callback);
});
 
SocketGroups.update = isOwner(function (socket, data, callback) {
	groups.update(data.groupName, data.values, callback);
});
 
 
SocketGroups.kick = isOwner(function (socket, data, callback) {
	if (socket.uid === parseInt(data.uid, 10)) {
		return callback(new Error('[[error:cant-kick-self]]'));
	}
 
	groups.ownership.isOwner(data.uid, data.groupName, function (err, isOwner) {
		if (err) {
			return callback(err);
		}
		groups.kick(data.uid, data.groupName, isOwner, callback);
	});
 
});
 
SocketGroups.create = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:no-privileges]]'));
	} else if (parseInt(meta.config.allowGroupCreation, 10) !== 1) {
		return callback(new Error('[[error:group-creation-disabled]]'));
	} else if (groups.isPrivilegeGroup(data.name)) {
		return callback(new Error('[[error:invalid-group-name]]'));
	}
 
 
	data.ownerUid = socket.uid;
	groups.create(data, callback);
};
 
SocketGroups.delete = function (socket, data, callback) {
	if (data.groupName === 'administrators' ||
		data.groupName === 'registered-users' ||
		data.groupName === 'Global Moderators') {
		return callback(new Error('[[error:not-allowed]]'));
	}
 
	async.parallel({
		isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName),
		isAdmin: async.apply(user.isAdministrator, socket.uid)
	}, function (err, checks) {
		if (err) {
			return callback(err);
		}
		if (!checks.isOwner && !checks.isAdmin) {
			return callback(new Error('[[error:no-privileges]]'));
		}
 
		groups.destroy(data.groupName, callback);
	});
};
 
SocketGroups.search = function (socket, data, callback) {
	data.options = data.options || {};
 
	if (!data.query) {
		var groupsPerPage = 15;
		groupsController.getGroupsFromSet(socket.uid, data.options.sort, 0, groupsPerPage - 1, function (err, data) {
			callback(err, !err ? data.groups : null);
		});
		return;
	}
 
	groups.search(data.query, data.options, callback);
};
 
SocketGroups.loadMore = function (socket, data, callback) {
	if (!data.sort  || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
		return callback();
	}
 
	var groupsPerPage = 9;
	var start = parseInt(data.after, 10);
	var stop = start + groupsPerPage - 1;
	groupsController.getGroupsFromSet(socket.uid, data.sort, start, stop, callback);
};
 
SocketGroups.searchMembers = function (socket, data, callback) {
	data.uid = socket.uid;
	groups.searchMembers(data, callback);
};
 
SocketGroups.loadMoreMembers = function (socket, data, callback) {
	if (!data.groupName || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	data.after = parseInt(data.after, 10);
	user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function (err, users) {
		if (err) {
			return callback(err);
		}
 
		callback(null, {users: users, nextStart: data.after + 10});
	});
};
 
SocketGroups.cover = {};
 
SocketGroups.cover.update = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:no-privileges]]'));
	}
 
	groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) {
		if (err || !isOwner) {
			return callback(err || new Error('[[error:no-privileges]]'));
		}
 
		groups.updateCover(socket.uid, data, callback);
	});
};
 
SocketGroups.cover.remove = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:no-privileges]]'));
	}
 
	groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) {
		if (err || !isOwner) {
			return callback(err || new Error('[[error:no-privileges]]'));
		}
 
		groups.removeCover(data, callback);
	});
};
 
module.exports = SocketGroups;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/index.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/index.js

Statements: 11.02% (13 / 118)      Branches: 0% (0 / 77)      Functions: 0% (0 / 24)      Lines: 11.02% (13 / 118)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231    2 2 2 2 2   2                                                             1                       1                       1                                                                                                                         1                   1                           1                                                       1                                                                                                            
"use strict";
 
var async = require('async');
var nconf = require('nconf');
var winston = require('winston');
var url = require('url');
var cookieParser = require('cookie-parser')(nconf.get('secret'));
 
var db = require('../database');
var logger = require('../logger');
var ratelimit = require('../middleware/ratelimit');
 
(function (Sockets) {
	var Namespaces = {};
	var io;
 
	Sockets.init = function (server) {
		requireModules();
 
		var SocketIO = require('socket.io');
		var socketioWildcard = require('socketio-wildcard')();
		io = new SocketIO({
			path: nconf.get('relative_path') + '/socket.io'
		});
 
		addRedisAdapter(io);
 
		io.use(socketioWildcard);
		io.use(authorize);
 
		io.on('connection', onConnection);
 
		io.listen(server, {
			transports: nconf.get('socket.io:transports')
		});
 
		Sockets.server = io;
	};
 
	function onConnection(socket) {
		socket.ip = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
 
		logger.io_one(socket, socket.uid);
 
		onConnect(socket);
 
		socket.on('*', function (payload) {
			onMessage(socket, payload);
		});
	}
 
	function onConnect(socket) {
		if (socket.uid) {
			socket.join('uid_' + socket.uid);
			socket.join('online_users');
		} else {
			socket.join('online_guests');
		}
 
		socket.join('sess_' + socket.request.signedCookies[nconf.get('sessionKey')]);
		io.sockets.sockets[socket.id].emit('checkSession', socket.uid);
	}
 
	function onMessage(socket, payload) {
		if (!payload.data.length) {
			return winston.warn('[socket.io] Empty payload');
		}
 
		var eventName = payload.data[0];
		var params = payload.data[1];
		var callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function () {
		};
 
		if (!eventName) {
			return winston.warn('[socket.io] Empty method name');
		}
 
		var parts = eventName.toString().split('.');
		var namespace = parts[0];
		var methodToCall = parts.reduce(function (prev, cur) {
			if (prev !== null && prev[cur]) {
				return prev[cur];
			} else {
				return null;
			}
		}, Namespaces);
 
		if (!methodToCall) {
			if (process.env.NODE_ENV === 'development') {
				winston.warn('[socket.io] Unrecognized message: ' + eventName);
			}
			return callback({message: '[[error:invalid-event]]'});
		}
 
		socket.previousEvents = socket.previousEvents || [];
		socket.previousEvents.push(eventName);
		if (socket.previousEvents.length > 20) {
			socket.previousEvents.shift();
		}
 
		if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) {
			winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents);
			return socket.disconnect();
		}
 
		async.waterfall([
			function (next) {
				validateSession(socket, next);
			},
			function (next) {
				if (Namespaces[namespace].before) {
					Namespaces[namespace].before(socket, eventName, params, next);
				} else {
					next();
				}
			},
			function (next) {
				methodToCall(socket, params, next);
			}
		], function (err, result) {
			callback(err ? {message: err.message} : null, result);
		});
	}
 
	function requireModules() {
		var modules = ['admin', 'categories', 'groups', 'meta', 'modules',
			'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist'
		];
 
		modules.forEach(function (module) {
			Namespaces[module] = require('./' + module);
		});
	}
 
	function validateSession(socket, callback) {
		var req = socket.request;
		if (!req.signedCookies || !req.signedCookies[nconf.get('sessionKey')]) {
			return callback(new Error('[[error:invalid-session]]'));
		}
		db.sessionStore.get(req.signedCookies[nconf.get('sessionKey')], function (err, sessionData) {
			if (err || !sessionData) {
				return callback(err || new Error('[[error:invalid-session]]'));
			}
 
			callback();
		});
	}
 
	function authorize(socket, callback) {
		var request = socket.request;
 
		if (!request) {
			return callback(new Error('[[error:not-authorized]]'));
		}
 
		async.waterfall([
			function (next) {
				cookieParser(request, {}, next);
			},
			function (next) {
				db.sessionStore.get(request.signedCookies[nconf.get('sessionKey')], function (err, sessionData) {
					if (err) {
						return next(err);
					}
					if (sessionData && sessionData.passport && sessionData.passport.user) {
						request.session = sessionData;
						socket.uid = parseInt(sessionData.passport.user, 10);
					} else {
						socket.uid = 0;
					}
					next();
				});
			}
		], callback);
	}
 
	function addRedisAdapter(io) {
		if (nconf.get('redis')) {
			var redisAdapter = require('socket.io-redis');
			var redis = require('../database/redis');
			var pub = redis.connect();
			var sub = redis.connect({return_buffers: true});
			io.adapter(redisAdapter({pubClient: pub, subClient: sub}));
		} else if (nconf.get('isCluster') === 'true') {
			winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.');
		}
	}
 
	Sockets.in = function (room) {
		return io.in(room);
	};
 
	Sockets.getUserSocketCount = function (uid) {
		if (!io) {
			return 0;
		}
 
		var room = io.sockets.adapter.rooms['uid_' + uid];
		return room ? room.length : 0;
	};
 
 
	Sockets.reqFromSocket = function (socket, payload, event) {
		var headers = socket.request ? socket.request.headers : {};
		var encrypted = socket.request ? !!socket.request.connection.encrypted : false;
		var host = headers.host;
		var referer = headers.referer || '';
		var data = ((payload || {}).data || []);
 
		if (!host) {
			host = url.parse(referer).host || '';
		}
 
		return {
			uid: socket.uid,
			params: data[1],
			method: event || data[0],
			body: payload,
			ip: headers['x-forwarded-for'] || socket.ip,
			host: host,
			protocol: encrypted ? 'https' : 'http',
			secure: encrypted,
			url: referer,
			path: referer.substr(referer.indexOf(host) + host.length),
			headers: headers
		};
	};
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/meta.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/meta.js

Statements: 5.71% (2 / 35)      Branches: 0% (0 / 23)      Functions: 0% (0 / 6)      Lines: 5.71% (2 / 35)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70      2                                                                                                       1                            
'use strict';
 
 
var user = require('../user');
var topics = require('../topics');
 
var SocketMeta = {
	rooms: {}
};
 
SocketMeta.reconnected = function (socket, data, callback) {
	callback = callback || function () {};
	if (socket.uid) {
		topics.pushUnreadCount(socket.uid);
		user.notifications.pushCount(socket.uid);
	}
	callback();
};
 
/* Rooms */
 
SocketMeta.rooms.enter = function (socket, data, callback) {
	if (!socket.uid) {
		return callback();
	}
 
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	if (data.enter) {
		data.enter = data.enter.toString();
	}
 
	if (data.enter && data.enter.startsWith('uid_') && data.enter !== 'uid_' + socket.uid) {
		return callback(new Error('[[error:not-allowed]]'));
	}
 
	leaveCurrentRoom(socket);
 
	if (data.enter) {
		socket.join(data.enter);
		socket.currentRoom = data.enter;
	}
	callback();
};
 
SocketMeta.rooms.leaveCurrent = function (socket, data, callback) {
	if (!socket.uid || !socket.currentRoom) {
		return callback();
	}
	leaveCurrentRoom(socket);
	callback();
};
 
function leaveCurrentRoom(socket) {
	if (socket.currentRoom) {
		socket.leave(socket.currentRoom);
		socket.currentRoom = '';
	}
}
 
SocketMeta.getServerTime = function (socket, data, callback) {
	// Returns server time in milliseconds
	callback(null, Date.now());
};
 
module.exports = SocketMeta;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/modules.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/modules.js

Statements: 2.4% (4 / 167)      Branches: 0% (0 / 114)      Functions: 0% (0 / 50)      Lines: 2.4% (4 / 167)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362  2 2   2                                                                                                                                                                       1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
"use strict";
var async = require('async');
var validator = require('validator');
 
var meta = require('../meta');
var notifications = require('../notifications');
var plugins = require('../plugins');
var Messaging = require('../messaging');
var utils = require('../../public/src/utils');
var server = require('./');
var user = require('../user');
 
var SocketModules = {
	chats: {},
	sounds: {},
	settings: {}
};
 
/* Chat */
 
SocketModules.chats.getRaw = function (socket, data, callback) {
	if (!data || !data.hasOwnProperty('mid')) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	async.waterfall([
		function (next) {
			Messaging.isUserInRoom(socket.uid, data.roomId, next);
		},
		function (inRoom, next) {
			if (!inRoom) {
				return next(new Error('[[error:not-allowed]]'));
			}
			Messaging.getMessageField(data.mid, 'content', next);
		}
	], callback);
};
 
SocketModules.chats.newRoom = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	if (rateLimitExceeded(socket)) {
		return callback(new Error('[[error:too-many-messages]]'));
	}
 
	Messaging.canMessageUser(socket.uid, data.touid, function (err) {
		if (err) {
			return callback(err);
		}
 
		Messaging.newRoom(socket.uid, [data.touid], callback);
	});
};
 
SocketModules.chats.send = function (socket, data, callback) {
	if (!data || !data.roomId || !socket.uid) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	if (rateLimitExceeded(socket)) {
		return callback(new Error('[[error:too-many-messages]]'));
	}
 
	async.waterfall([
		function (next) {
			plugins.fireHook('filter:messaging.send', {
				data: data,
				uid: socket.uid
			}, function (err, results) {
				data = results.data;
				next(err);
			});
		},
		function (next) {
			Messaging.canMessageRoom(socket.uid, data.roomId, next);
		},
		function (next) {
			Messaging.sendMessage(socket.uid, data.roomId, data.message, Date.now(), next);
		},
		function (message, next) {
			Messaging.notifyUsersInRoom(socket.uid, data.roomId, message);
			user.updateOnlineUsers(socket.uid);
			next(null, message);
		}
	], callback);
};
 
function rateLimitExceeded(socket) {
	var now = Date.now();
	socket.lastChatMessageTime = socket.lastChatMessageTime || 0;
	var delay = meta.config.hasOwnProperty('chatMessageDelay') ? parseInt(meta.config.chatMessageDelay, 10) : 200;
	if (now - socket.lastChatMessageTime < delay) {
		return true;
	} else {
		socket.lastChatMessageTime = now;
	}
	return false;
}
 
SocketModules.chats.loadRoom = function (socket, data, callback) {
	if (!data || !data.roomId) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.waterfall([
		function (next) {
			Messaging.isUserInRoom(socket.uid, data.roomId, next);
		},
		function (inRoom, next) {
			if (!inRoom) {
				return next(new Error('[[error:not-allowed]]'));
			}
 
			async.parallel({
				roomData: async.apply(Messaging.getRoomData, data.roomId),
				canReply: async.apply(Messaging.canReply, data.roomId, socket.uid),
				users: async.apply(Messaging.getUsersInRoom, data.roomId, 0, -1),
				messages: async.apply(Messaging.getMessages, {
					callerUid: socket.uid,
					uid: data.uid || socket.uid,
					roomId: data.roomId,
					isNew: false
				}),
			}, next);
		},
		function (results, next) {
			results.roomData.users = results.users;
			results.roomData.canReply = results.canReply;
			results.roomData.usernames = Messaging.generateUsernames(results.users, socket.uid);
			results.roomData.messages = results.messages;
			results.roomData.groupChat = results.roomData.hasOwnProperty('groupChat') ? results.roomData.groupChat : results.users.length > 2;
			results.roomData.isOwner = parseInt(results.roomData.owner, 10) === socket.uid;
			results.roomData.maximumUsersInChatRoom = parseInt(meta.config.maximumUsersInChatRoom, 10) || 0;
			results.roomData.showUserInput = !results.roomData.maximumUsersInChatRoom || results.roomData.maximumUsersInChatRoom > 2;
			next(null, results.roomData);
		}
	], callback);
};
 
SocketModules.chats.addUserToRoom = function (socket, data, callback) {
	if (!data || !data.roomId || !data.username) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	var uid;
	async.waterfall([
		function (next) {
			Messaging.getUserCountInRoom(data.roomId, next);
		},
		function (userCount, next) {
			var maxUsers = parseInt(meta.config.maximumUsersInChatRoom, 10) || 0;
			if (maxUsers && userCount >= maxUsers) {
				return next(new Error('[[error:cant-add-more-users-to-chat-room]]'));
			}
			next();
		},
		function (next) {
			user.getUidByUsername(data.username, next);
		},
		function (_uid, next) {
			uid = _uid;
			if (!uid) {
				return next(new Error('[[error:no-user]]'));
			}
			if (socket.uid === parseInt(uid, 10)) {
				return next(new Error('[[error:cant-add-self-to-chat-room]]'));
			}
			async.parallel({
				settings: async.apply(user.getSettings, uid),
				isAdminOrGlobalMod: async.apply(user.isAdminOrGlobalMod, socket.uid),
				isFollowing: async.apply(user.isFollowing, uid, socket.uid)
			}, next);
		},
		function (results, next) {
			if (results.settings.restrictChat && !results.isAdminOrGlobalMod && !results.isFollowing) {
				return next(new Error('[[error:chat-restricted]]'));
			}
 
			Messaging.addUsersToRoom(socket.uid, [uid], data.roomId, next);
		}
	], callback);
};
 
SocketModules.chats.removeUserFromRoom = function (socket, data, callback) {
	if (!data || !data.roomId) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	async.waterfall([
		function (next) {
			user.getUidByUsername(data.username, next);
		},
		function (uid, next) {
			if (!uid) {
				return next(new Error('[[error:no-user]]'));
			}
 
			Messaging.removeUsersFromRoom(socket.uid, [uid], data.roomId, next);
		}
	], callback);
};
 
SocketModules.chats.leave = function (socket, roomid, callback) {
	if (!socket.uid || !roomid) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	Messaging.leaveRoom([socket.uid], roomid, callback);
};
 
 
SocketModules.chats.edit = function (socket, data, callback) {
	if (!data || !data.roomId) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	Messaging.canEdit(data.mid, socket.uid, function (err, allowed) {
		if (err || !allowed) {
			return callback(err || new Error('[[error:cant-edit-chat-message]]'));
		}
 
		Messaging.editMessage(socket.uid, data.mid, data.roomId, data.message, callback);
	});
};
 
SocketModules.chats.delete = function (socket, data, callback) {
	if (!data || !data.roomId || !data.messageId) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	Messaging.canEdit(data.messageId, socket.uid, function (err, allowed) {
		if (err || !allowed) {
			return callback(err || new Error('[[error:cant-delete-chat-message]]'));
		}
 
		Messaging.deleteMessage(data.messageId, data.roomId, callback);
	});
};
 
SocketModules.chats.canMessage = function (socket, roomId, callback) {
	Messaging.canMessageRoom(socket.uid, roomId, callback);
};
 
SocketModules.chats.markRead = function (socket, roomId, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	async.parallel({
		uidsInRoom: async.apply(Messaging.getUidsInRoom, roomId, 0, -1),
		markRead: async.apply(Messaging.markRead, socket.uid, roomId)
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		Messaging.pushUnreadCount(socket.uid);
		server.in('uid_' + socket.uid).emit('event:chats.markedAsRead', {roomId: roomId});
 
		if (results.uidsInRoom.indexOf(socket.uid.toString()) === -1) {
			return callback();
		}
 
		// Mark notification read
		var nids = results.uidsInRoom.filter(function (uid) {
			return parseInt(uid, 10) !== socket.uid;
		}).map(function (uid) {
			return 'chat_' + uid + '_' + roomId;
		});
 
		notifications.markReadMultiple(nids, socket.uid, function () {
			user.notifications.pushCount(socket.uid);
		});
 
		callback();
	});
};
 
SocketModules.chats.markAllRead = function (socket, data, callback) {
	async.waterfall([
		function (next) {
			Messaging.markAllRead(socket.uid, next);
		},
		function (next) {
			Messaging.pushUnreadCount(socket.uid);
			next();
		}
	], callback);
};
 
SocketModules.chats.renameRoom = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-name]]'));
	}
 
	async.waterfall([
		function (next) {
			Messaging.renameRoom(socket.uid, data.roomId, data.newName, next);
		},
		function (next) {
			Messaging.getUidsInRoom(data.roomId, 0, -1, next);
		},
		function (uids, next) {
			var eventData = {roomId: data.roomId, newName: validator.escape(String(data.newName))};
			uids.forEach(function (uid) {
				server.in('uid_' + uid).emit('event:chats.roomRename', eventData);
			});
			next();
		}
	], callback);
};
 
SocketModules.chats.getRecentChats = function (socket, data, callback) {
	if (!data || !utils.isNumber(data.after) || !utils.isNumber(data.uid)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	var start = parseInt(data.after, 10);
	var stop = start + 9;
	Messaging.getRecentChats(socket.uid, data.uid, start, stop, callback);
};
 
SocketModules.chats.hasPrivateChat = function (socket, uid, callback) {
	if (!socket.uid || !uid) {
		return callback(null, new Error('[[error:invalid-data]]'));
	}
	Messaging.hasPrivateChat(socket.uid, uid, callback);
};
 
SocketModules.chats.getMessages = function (socket, data, callback) {
	if (!socket.uid || !data.uid || !data.roomId) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	var params = {
		callerUid: socket.uid,
		uid: data.uid,
		roomId: data.roomId,
		start: parseInt(data.start, 10) || 0,
		count: 50
	};
 
	Messaging.getMessages(params, callback);
};
 
/* Sounds */
SocketModules.sounds.getSounds = function (socket, data, callback) {
	// Read sounds from local directory
	meta.sounds.getFiles(callback);
};
 
SocketModules.sounds.getMapping = function (socket, data, callback) {
	meta.sounds.getMapping(socket.uid, callback);
};
 
SocketModules.sounds.getData = function (socket, data, callback) {
	async.parallel({
		mapping: async.apply(meta.sounds.getMapping, socket.uid),
		files: async.apply(meta.sounds.getFiles)
	}, callback);
};
 
module.exports = SocketModules;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/notifications.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/notifications.js

Statements: 6.06% (2 / 33)      Branches: 0% (0 / 16)      Functions: 0% (0 / 8)      Lines: 6.06% (2 / 33)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61    2 2                                                                                                                  
"use strict";
 
var async = require('async');
var user = require('../user');
var notifications = require('../notifications');
var utils = require('../../public/src/utils');
 
var SocketNotifs = {};
 
SocketNotifs.get = function (socket, data, callback) {
	if (data && Array.isArray(data.nids) && socket.uid) {
		user.notifications.getNotifications(data.nids, socket.uid, callback);
	} else {
		user.notifications.get(socket.uid, callback);
	}
};
 
SocketNotifs.loadMore = function (socket, data, callback) {
	if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	if (!socket.uid) {
		return callback(new Error('[[error:no-privileges]]'));
	}
	var start = parseInt(data.after, 10);
	var stop = start + 20;
	user.notifications.getAll(socket.uid, start, stop, function (err, notifications) {
		if (err) {
			return callback(err);
		}
		callback(null, {notifications: notifications, nextStart: stop});
	});
};
 
SocketNotifs.getCount = function (socket, data, callback) {
	user.notifications.getUnreadCount(socket.uid, callback);
};
 
SocketNotifs.deleteAll = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:no-privileges]]'));
	}
 
	user.notifications.deleteAll(socket.uid, callback);
};
 
SocketNotifs.markRead = function (socket, nid, callback) {
	notifications.markRead(nid, socket.uid, callback);
};
 
SocketNotifs.markUnread = function (socket, nid, callback) {
	notifications.markUnread(nid, socket.uid, callback);
};
 
SocketNotifs.markAllRead = function (socket, data, callback) {
	notifications.markAllRead(socket.uid, callback);
};
 
module.exports = SocketNotifs;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/plugins.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/plugins.js

Statements: 100% (2 / 2)      Branches: 100% (0 / 0)      Functions: 100% (0 / 0)      Lines: 100% (2 / 2)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19    1                           1    
'use strict';
 
var	SocketPlugins = {};
 
/*
	This file is provided exclusively so that plugins can require it and add their own socket listeners.
 
	How? From your plugin:
 
		var SocketPlugins = require.main.require('./src/socket.io/plugins');
		SocketPlugins.myPlugin = {};
		SocketPlugins.myPlugin.myMethod = function(socket, data, callback) { ... };
 
	Be a good lad and namespace your methods.
*/
 
module.exports = SocketPlugins;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts.js

Statements: 3.7% (3 / 81)      Branches: 0% (0 / 22)      Functions: 0% (0 / 24)      Lines: 3.7% (3 / 81)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159    2   2                                                                                                                                                                                           1                                                                                                                        
"use strict";
 
var	async = require('async');
 
var posts = require('../posts');
var privileges = require('../privileges');
var meta = require('../meta');
var topics = require('../topics');
var user = require('../user');
var websockets = require('./index');
var socketHelpers = require('./helpers');
var utils = require('../../public/src/utils');
 
var apiController = require('../controllers/api');
 
var SocketPosts = {};
 
require('./posts/edit')(SocketPosts);
require('./posts/move')(SocketPosts);
require('./posts/votes')(SocketPosts);
require('./posts/bookmarks')(SocketPosts);
require('./posts/tools')(SocketPosts);
require('./posts/flag')(SocketPosts);
 
SocketPosts.reply = function (socket, data, callback) {
	if (!data || !data.tid || !data.content) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	data.uid = socket.uid;
	data.req = websockets.reqFromSocket(socket);
	data.timestamp = Date.now();
 
	topics.reply(data, function (err, postData) {
		if (err) {
			return callback(err);
		}
 
		var result = {
			posts: [postData],
			'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1,
			'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1,
		};
 
		callback(null, postData);
 
		websockets.in('uid_' + socket.uid).emit('event:new_post', result);
 
		user.updateOnlineUsers(socket.uid);
 
		socketHelpers.notifyNew(socket.uid, 'newPost', result);
	});
};
 
SocketPosts.getRawPost = function (socket, pid, callback) {
	async.waterfall([
		function (next) {
			privileges.posts.can('read', pid, socket.uid, next);
		},
		function (canRead, next) {
			if (!canRead) {
				return next(new Error('[[error:no-privileges]]'));
			}
			posts.getPostFields(pid, ['content', 'deleted'], next);
		},
		function (postData, next) {
			if (parseInt(postData.deleted, 10) === 1) {
				return next(new Error('[[error:no-post]]'));
			}
			next(null, postData.content);
		}
	], callback);
};
 
SocketPosts.getPost = function (socket, pid, callback) {
	apiController.getPostData(pid, socket.uid, callback);
};
 
SocketPosts.loadMoreBookmarks = function (socket, data, callback) {
	loadMorePosts('uid:' + data.uid + ':bookmarks', socket.uid, data, callback);
};
 
SocketPosts.loadMoreUserPosts = function (socket, data, callback) {
	loadMorePosts('uid:' + data.uid + ':posts', socket.uid, data, callback);
};
 
SocketPosts.loadMoreBestPosts = function (socket, data, callback) {
	loadMorePosts('uid:' + data.uid + ':posts:votes', socket.uid, data, callback);
};
 
SocketPosts.loadMoreUpVotedPosts = function (socket, data, callback) {
	loadMorePosts('uid:' + data.uid + ':upvote', socket.uid, data, callback);
};
 
SocketPosts.loadMoreDownVotedPosts = function (socket, data, callback) {
	loadMorePosts('uid:' + data.uid + ':downvote', socket.uid, data, callback);
};
 
function loadMorePosts(set, uid, data, callback) {
	if (!data || !utils.isNumber(data.uid) || !utils.isNumber(data.after)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	var start = Math.max(0, parseInt(data.after, 10));
	var stop = start + 9;
 
	posts.getPostSummariesFromSet(set, uid, start, stop, callback);
}
 
SocketPosts.getCategory = function (socket, pid, callback) {
	posts.getCidByPid(pid, callback);
};
 
SocketPosts.getPidIndex = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	posts.getPidIndex(data.pid, data.tid, data.topicPostSort, callback);
};
 
SocketPosts.getReplies = function (socket, pid, callback) {
	if (!utils.isNumber(pid)) {
		return callback(new Error('[[error:invalid-data]'));
	}
	var postPrivileges;
	async.waterfall([
		function (next) {
			posts.getPidsFromSet('pid:' + pid + ':replies', 0, -1, false, next);
		},
		function (pids, next) {
			async.parallel({
				posts: function (next) {
					posts.getPostsByPids(pids, socket.uid, next);
				},
				privileges: function (next) {
					privileges.posts.get(pids, socket.uid, next);
				}
			}, next);
		},
		function (results, next) {
			postPrivileges = results.privileges;
			results.posts = results.posts.filter(function (postData, index) {
				return postData && postPrivileges[index].read;
			});
			topics.addPostData(results.posts, socket.uid, next);
		},
		function (postData, next) {
			postData.forEach(function (postData) {
				posts.modifyPostByPrivilege(postData, postPrivileges.isAdminOrMod);
			});
			next(null, postData);
		}
	], callback);
};
 
 
module.exports = SocketPosts;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics.js

Statements: 4.76% (3 / 63)      Branches: 0% (0 / 30)      Functions: 0% (0 / 14)      Lines: 4.76% (3 / 63)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114      2   2                                                                                                                                                     1                                                                  
 
'use strict';
 
var async = require('async');
 
var topics = require('../topics');
var websockets = require('./index');
var user = require('../user');
var apiController = require('../controllers/api');
var socketHelpers = require('./helpers');
 
var SocketTopics = {};
 
require('./topics/unread')(SocketTopics);
require('./topics/move')(SocketTopics);
require('./topics/tools')(SocketTopics);
require('./topics/infinitescroll')(SocketTopics);
require('./topics/tags')(SocketTopics);
 
SocketTopics.post = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	data.uid = socket.uid;
	data.req = websockets.reqFromSocket(socket);
	data.timestamp = Date.now();
 
	topics.post(data, function (err, result) {
		if (err) {
			return callback(err);
		}
 
		callback(null, result.topicData);
 
		socket.emit('event:new_post', {posts: [result.postData]});
		socket.emit('event:new_topic', result.topicData);
 
		socketHelpers.notifyNew(socket.uid, 'newTopic', {posts: [result.postData], topic: result.topicData});
	});
};
 
SocketTopics.postcount = function (socket, tid, callback) {
	topics.getTopicField(tid, 'postcount', callback);
};
 
SocketTopics.bookmark = function (socket, data, callback) {
	if (!socket.uid || !data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	topics.setUserBookmark(data.tid, socket.uid, data.index, callback);
};
 
SocketTopics.createTopicFromPosts = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:not-logged-in]]'));
	}
 
	if (!data || !data.title || !data.pids || !Array.isArray(data.pids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	topics.createTopicFromPosts(socket.uid, data.title, data.pids, data.fromTid, callback);
};
 
SocketTopics.changeWatching = function (socket, data, callback) {
	if (!data.tid || !data.type) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	var commands = ['follow', 'unfollow', 'ignore'];
	if (commands.indexOf(data.type) === -1) {
		return callback(new Error('[[error:invalid-command]]'));
	}
	followCommand(topics[data.type], socket, data.tid, callback);
};
 
SocketTopics.follow = function (socket, tid, callback) {
	followCommand(topics.follow, socket, tid, callback);
};
 
function followCommand(method, socket, tid, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:not-logged-in]]'));
	}
 
	method(tid, socket.uid, callback);
}
 
SocketTopics.isFollowed = function (socket, tid, callback) {
	topics.isFollowing([tid], socket.uid, function (err, isFollowing) {
		callback(err, Array.isArray(isFollowing) && isFollowing.length ? isFollowing[0] : false);
	});
};
 
SocketTopics.search = function (socket, data, callback) {
	topics.search(data.tid, data.term, callback);
};
 
SocketTopics.isModerator = function (socket, tid, callback) {
	topics.getTopicField(tid, 'cid', function (err, cid) {
		if (err) {
			return callback(err);
		}
		user.isModerator(socket.uid, cid, callback);
	});
};
 
SocketTopics.getTopic = function (socket, tid, callback) {
	apiController.getTopicData(tid, socket.uid, callback);
};
 
module.exports = SocketTopics;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user.js

Statements: 2.38% (4 / 168)      Branches: 0% (0 / 104)      Functions: 0% (0 / 42)      Lines: 2.38% (4 / 168)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351    2 2   2                                                                                                                                                                                                                                                                                                                                                                                       1                                                                                                                                                                                                                                                                                                                          
'use strict';
 
var async = require('async');
var winston = require('winston');
 
var user = require('../user');
var topics = require('../topics');
var notifications = require('../notifications');
var messaging = require('../messaging');
var plugins = require('../plugins');
var meta = require('../meta');
var events = require('../events');
var emailer = require('../emailer');
var db = require('../database');
var apiController = require('../controllers/api');
var privileges = require('../privileges');
 
var SocketUser = {};
 
require('./user/profile')(SocketUser);
require('./user/search')(SocketUser);
require('./user/status')(SocketUser);
require('./user/picture')(SocketUser);
require('./user/ban')(SocketUser);
 
SocketUser.exists = function (socket, data, callback) {
	if (!data || !data.username) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	meta.userOrGroupExists(data.username, callback);
};
 
SocketUser.deleteAccount = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:no-privileges]]'));
	}
 
	async.waterfall([
		function (next) {
			user.isAdministrator(socket.uid, next);
		},
		function (isAdmin, next) {
			if (isAdmin) {
				return next(new Error('[[error:cant-delete-admin]]'));
			}
			user.deleteAccount(socket.uid, next);
		},
		function (next) {
			require('./index').server.sockets.emit('event:user_status_change', {uid: socket.uid, status: 'offline'});
 
			events.log({
				type: 'user-delete',
				uid: socket.uid,
				targetUid: socket.uid,
				ip: socket.ip
			});
			next();
		}
	], callback);
};
 
SocketUser.emailExists = function (socket, data, callback) {
	if (!data || !data.email) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	user.email.exists(data.email, callback);
};
 
SocketUser.emailConfirm = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:no-privileges]]'));
	}
 
	if (parseInt(meta.config.requireEmailConfirmation, 10) !== 1) {
		callback();
	}
	user.getUserField(socket.uid, 'email', function (err, email) {
		if (err || !email) {
			return callback(err);
		}
 
		user.email.sendValidationEmail(socket.uid, email, callback);
	});
};
 
 
// Password Reset
SocketUser.reset = {};
 
SocketUser.reset.send = function (socket, email, callback) {
	if (!email) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	user.reset.send(email, function (err) {
		if (err && err.message !== '[[error:invalid-email]]') {
			return callback(err);
		}
		if (err && err.message === '[[error:invalid-email]]') {
			winston.verbose('[user/reset] Invalid email attempt: ' + email);
			return setTimeout(callback, 2500);
		}
 
		callback();
	});
};
 
SocketUser.reset.commit = function (socket, data, callback) {
	if (!data || !data.code || !data.password) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.parallel({
		uid: async.apply(db.getObjectField, 'reset:uid', data.code),
		reset: async.apply(user.reset.commit, data.code, data.password)
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		var uid = results.uid;
		var now = new Date();
		var parsedDate = now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate();
 
		user.getUserField(uid, 'username', function (err, username) {
			if (err) {
				return callback(err);
			}
 
			emailer.send('reset_notify', uid, {
				username: username,
				date: parsedDate,
				site_title: meta.config.title || 'NodeBB',
				subject: '[[email:reset.notify.subject]]'
			});
		});
 
		events.log({
			type: 'password-reset',
			uid: uid,
			ip: socket.ip
		});
		callback();
	});
};
 
SocketUser.isFollowing = function (socket, data, callback) {
	if (!socket.uid || !data.uid) {
		return callback(null, false);
	}
 
	user.isFollowing(socket.uid, data.uid, callback);
};
 
SocketUser.follow = function (socket, data, callback) {
	if (!socket.uid || !data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	var userData;
	async.waterfall([
		function (next) {
			toggleFollow('follow', socket.uid, data.uid, next);
		},
		function (next) {
			user.getUserFields(socket.uid, ['username', 'userslug'], next);
		},
		function (_userData, next) {
			userData = _userData;
			notifications.create({
				bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]',
				nid: 'follow:' + data.uid + ':uid:' + socket.uid,
				from: socket.uid,
				path: '/uid/' + data.uid + '/followers',
				mergeId: 'notifications:user_started_following_you'
			}, next);
		},
		function (notification, next) {
			if (!notification) {
				return next();
			}
			notification.user = userData;
			notifications.push(notification, [data.uid], next);
		}
	], callback);
};
 
SocketUser.unfollow = function (socket, data, callback) {
	if (!socket.uid || !data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	toggleFollow('unfollow', socket.uid, data.uid, callback);
};
 
function toggleFollow(method, uid, theiruid, callback) {
	user[method](uid, theiruid, function (err) {
		if (err) {
			return callback(err);
		}
 
		plugins.fireHook('action:user.' + method, {
			fromUid: uid,
			toUid: theiruid
		});
		callback();
	});
}
 
SocketUser.saveSettings = function (socket, data, callback) {
	if (!socket.uid || !data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.waterfall([
		function (next) {
			privileges.users.canEdit(socket.uid, data.uid, next);
		},
		function (allowed, next) {
			if (!allowed) {
				return next(new Error('[[error:no-privileges]]'));
			}
			user.saveSettings(data.uid, data.settings, next);
		}
	], callback);
};
 
SocketUser.setTopicSort = function (socket, sort, callback) {
	if (!socket.uid) {
		return callback();
	}
	user.setSetting(socket.uid, 'topicPostSort', sort, callback);
};
 
SocketUser.setCategorySort = function (socket, sort, callback) {
	if (!socket.uid) {
		return callback();
	}
	user.setSetting(socket.uid, 'categoryTopicSort', sort, callback);
};
 
SocketUser.getUnreadCount = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(null, 0);
	}
	topics.getTotalUnread(socket.uid, callback);
};
 
SocketUser.getUnreadChatCount = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(null, 0);
	}
	messaging.getUnreadCount(socket.uid, callback);
};
 
SocketUser.getUnreadCounts = function (socket, data, callback) {
	if (!socket.uid) {
		return callback(null, {});
	}
	async.parallel({
		unreadTopicCount: async.apply(topics.getTotalUnread, socket.uid),
		unreadNewTopicCount: async.apply(topics.getTotalUnread, socket.uid, 'new'),
		unreadChatCount: async.apply(messaging.getUnreadCount, socket.uid),
		unreadNotificationCount: async.apply(user.notifications.getUnreadCount, socket.uid)
	}, callback);
};
 
SocketUser.invite = function (socket, email, callback) {
	if (!email || !socket.uid) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	var registrationType = meta.config.registrationType;
 
	if (registrationType !== 'invite-only' && registrationType !== 'admin-invite-only') {
		return callback(new Error('[[error:forum-not-invite-only]]'));
	}
 
	var max = meta.config.maximumInvites;
 
	user.isAdministrator(socket.uid, function (err, admin) {
		if (err) {
			return callback(err);
		}
		if (registrationType === 'admin-invite-only' && !admin) {
			return callback(new Error('[[error:no-privileges]]'));
		}
		if (max) {
			async.waterfall([
				function (next) {
					user.getInvitesNumber(socket.uid, next);
				},
				function (invites, next) {
					if (!admin && invites > max) {
						return next(new Error('[[error:invite-maximum-met, ' + invites + ', ' + max + ']]'));
					}
					next();
				},
				function (next) {
					user.sendInvitationEmail(socket.uid, email, next);
				}
			], callback);
		} else {
			user.sendInvitationEmail(socket.uid, email, callback);
		}
	});
 
};
 
SocketUser.getUserByUID = function (socket, uid, callback) {
	apiController.getUserDataByField(socket.uid, 'uid', uid, callback);
};
 
SocketUser.getUserByUsername = function (socket, username, callback) {
	apiController.getUserDataByField(socket.uid, 'username', username, callback);
};
 
SocketUser.getUserByEmail = function (socket, email, callback) {
	apiController.getUserDataByField(socket.uid, 'email', email, callback);
};
 
SocketUser.setModerationNote = function (socket, data, callback) {
	if (!socket.uid || !data || !data.uid) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.waterfall([
		function (next) {
			privileges.users.canEdit(socket.uid, data.uid, next);
		},
		function (allowed, next) {
			if (allowed) {
				return next(null, allowed);
			}
 
			user.isModeratorOfAnyCategory(socket.uid, next);
		},
		function (allowed, next) {
			if (!allowed) {
				return next(new Error('[[error:no-privileges]]'));
			}
			if (data.note) {
				user.setUserField(data.uid, 'moderationNote', data.note, next);
			} else {
				db.deleteObjectField('user:' + data.uid, 'moderationNote', next);
			}
		}
	], callback);
};
 
module.exports = SocketUser;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/

Statements: 5% (16 / 320)      Branches: 0% (0 / 134)      Functions: 0% (0 / 89)      Lines: 5% (16 / 320)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/
File Statements Branches Functions Lines
categories.js 5.66% (3 / 53) 0% (0 / 18) 0% (0 / 19) 5.66% (3 / 53)
groups.js 6.25% (2 / 32) 0% (0 / 18) 0% (0 / 8) 6.25% (2 / 32)
rooms.js 5.43% (5 / 92) 0% (0 / 34) 0% (0 / 17) 5.43% (5 / 92)
social.js 25% (1 / 4) 100% (0 / 0) 0% (0 / 1) 25% (1 / 4)
tags.js 7.69% (1 / 13) 0% (0 / 4) 0% (0 / 3) 7.69% (1 / 13)
user.js 3.17% (4 / 126) 0% (0 / 60) 0% (0 / 41) 3.17% (4 / 126)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/categories.js

Statements: 5.66% (3 / 53)      Branches: 0% (0 / 18)      Functions: 0% (0 / 19)      Lines: 5.66% (3 / 53)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107    2   2                                                                                                                                                                   1                                        
"use strict";
 
var async = require('async');
 
var db = require('../../database');
var groups = require('../../groups');
var categories = require('../../categories');
var privileges = require('../../privileges');
var plugins = require('../../plugins');
var Categories = {};
 
Categories.create = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	categories.create(data, callback);
};
 
Categories.getAll = function (socket, data, callback) {
	async.waterfall([
		async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
		async.apply(categories.getCategoriesData),
		function (categories, next) {
			//Hook changes, there is no req, and res
			plugins.fireHook('filter:admin.categories.get', {categories: categories}, next);
		},
		function (result, next) {
			next(null, categories.getTree(result.categories, 0));
		}
	], function (err, categoriesTree) {
		if (err) {
			return callback(err);
		}
 
		callback(null, categoriesTree);
	});
};
 
Categories.getNames = function (socket, data, callback) {
	categories.getAllCategoryFields(['cid', 'name'], callback);
};
 
Categories.purge = function (socket, cid, callback) {
	categories.purge(cid, socket.uid, callback);
};
 
Categories.update = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	categories.update(data, callback);
};
 
Categories.setPrivilege = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	if (Array.isArray(data.privilege)) {
		async.each(data.privilege, function (privilege, next) {
			groups[data.set ? 'join' : 'leave']('cid:' + data.cid + ':privileges:' + privilege, data.member, next);
		}, callback);
	} else {
		groups[data.set ? 'join' : 'leave']('cid:' + data.cid + ':privileges:' + data.privilege, data.member, callback);
	}
};
 
Categories.getPrivilegeSettings = function (socket, cid, callback) {
	privileges.categories.list(cid, callback);
};
 
Categories.copyPrivilegesToChildren = function (socket, cid, callback) {
	categories.getCategories([cid], socket.uid, function (err, categories) {
		if (err) {
			return callback(err);
		}
		var category = categories[0];
 
		async.eachSeries(category.children, function (child, next) {
			copyPrivilegesToChildrenRecursive(cid, child, next);
		}, callback);
	});
};
 
function copyPrivilegesToChildrenRecursive(parentCid, category, callback) {
	categories.copyPrivilegesFrom(parentCid, category.cid, function (err) {
		if (err) {
			return callback(err);
		}
		async.eachSeries(category.children, function (child, next) {
			copyPrivilegesToChildrenRecursive(parentCid, child, next);
		}, callback);
	});
}
 
Categories.copySettingsFrom = function (socket, data, callback) {
	categories.copySettingsFrom(data.fromCid, data.toCid, true, callback);
};
 
Categories.copyPrivilegesFrom = function (socket, data, callback) {
	categories.copyPrivilegesFrom(data.fromCid, data.toCid, callback);
};
 
module.exports = Categories;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/groups.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/groups.js

Statements: 6.25% (2 / 32)      Branches: 0% (0 / 18)      Functions: 0% (0 / 8)      Lines: 6.25% (2 / 32)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71    2 2                                                                                                                                      
"use strict";
 
var async = require('async');
var groups = require('../../groups');
 
var Groups = {};
 
Groups.create = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	} else if (groups.isPrivilegeGroup(data.name)) {
		return callback(new Error('[[error:invalid-group-name]]'));
	}
 
	groups.create({
		name: data.name,
		description: data.description,
		ownerUid: socket.uid
	}, callback);
};
 
Groups.join = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.waterfall([
		function (next) {
			groups.isMember(data.uid, data.groupName, next);
		},
		function (isMember, next) {
			if (isMember) {
				return next(new Error('[[error:group-already-member]]'));
			}
			groups.join(data.groupName, data.uid, next);
		}
	], callback);
};
 
Groups.leave = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	if (socket.uid === parseInt(data.uid, 10) && data.groupName === 'administrators') {
		return callback(new Error('[[error:cant-remove-self-as-admin]]'));
	}
 
	async.waterfall([
		function (next) {
			groups.isMember(data.uid, data.groupName, next);
		},
		function (isMember, next) {
			if (!isMember) {
				return next(new Error('[[error:group-not-member]]'));
			}
			groups.leave(data.groupName, data.uid, next);
		}
	], callback);
};
 
Groups.update = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	groups.update(data.groupName, data.values, callback);
};
 
module.exports = Groups;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/rooms.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/rooms.js

Statements: 5.43% (5 / 92)      Branches: 0% (0 / 34)      Functions: 0% (0 / 17)      Lines: 5.43% (5 / 92)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179      4 4 4 4 4                                                                                                                                                                                                                                                                                                                                                      
'use strict';
 
 
var os = require('os');
var nconf = require('nconf');
var winston = require('winston');
var validator = require('validator');
var topics = require('../../topics');
var pubsub = require('../../pubsub');
 
var stats = {};
var totals = {};
var SocketRooms = {
	stats: stats,
	totals: totals
};
 
 
pubsub.on('sync:stats:start', function () {
	SocketRooms.getLocalStats(function (err, stats) {
		if (err) {
			return winston.error(err);
		}
		pubsub.publish('sync:stats:end', {stats: stats, id: os.hostname() + ':' + nconf.get('port')});
	});
});
 
pubsub.on('sync:stats:end', function (data) {
	stats[data.id] = data.stats;
});
 
pubsub.on('sync:stats:guests', function () {
	var io = require('../index').server;
 
	var roomClients = io.sockets.adapter.rooms;
	var guestCount = roomClients.online_guests ? roomClients.online_guests.length : 0;
	pubsub.publish('sync:stats:guests:end', guestCount);
});
 
SocketRooms.getTotalGuestCount = function (callback) {
	var count = 0;
 
	pubsub.on('sync:stats:guests:end', function (guestCount) {
		count += guestCount;
	});
 
	pubsub.publish('sync:stats:guests');
 
	setTimeout(function () {
		pubsub.removeAllListeners('sync:stats:guests:end');
		callback(null, count);
	}, 100);
};
 
 
SocketRooms.getAll = function (socket, data, callback) {
	pubsub.publish('sync:stats:start');
 
	totals.onlineGuestCount = 0;
	totals.onlineRegisteredCount = 0;
	totals.socketCount = 0;
	totals.topics = {};
	totals.users = {
		categories: 0,
		recent: 0,
		unread: 0,
		topics: 0,
		category: 0
	};
 
	for(var instance in stats) {
		if (stats.hasOwnProperty(instance)) {
			totals.onlineGuestCount += stats[instance].onlineGuestCount;
			totals.onlineRegisteredCount += stats[instance].onlineRegisteredCount;
			totals.socketCount += stats[instance].socketCount;
			totals.users.categories += stats[instance].users.categories;
			totals.users.recent += stats[instance].users.recent;
			totals.users.unread += stats[instance].users.unread;
			totals.users.topics += stats[instance].users.topics;
			totals.users.category += stats[instance].users.category;
 
			stats[instance].topics.forEach(function (topic) {
				totals.topics[topic.tid] = totals.topics[topic.tid] || {count: 0, tid: topic.tid};
				totals.topics[topic.tid].count += topic.count;
			});
		}
	}
 
	var topTenTopics = [];
	Object.keys(totals.topics).forEach(function (tid) {
		topTenTopics.push({tid: tid, count: totals.topics[tid].count});
	});
 
	topTenTopics = topTenTopics.sort(function (a, b) {
		return b.count - a.count;
	}).slice(0, 10);
 
	var topTenTids = topTenTopics.map(function (topic) {
		return topic.tid;
	});
 
	topics.getTopicsFields(topTenTids, ['title'], function (err, titles) {
		if (err) {
			return callback(err);
		}
		totals.topics = {};
		topTenTopics.forEach(function (topic, index) {
			totals.topics[topic.tid] = {
				value: topic.count || 0,
				title: validator.escape(String(titles[index].title))
			};
		});
 
		callback(null, totals);
	});
};
 
SocketRooms.getOnlineUserCount = function (io) {
	if (!io) {
		return 0;
	}
	var count = 0;
	for (var key in io.sockets.adapter.rooms) {
		if (io.sockets.adapter.rooms.hasOwnProperty(key) && key.startsWith('uid_')) {
			++ count;
		}
	}
 
	return count;
};
 
SocketRooms.getLocalStats = function (callback) {
	var io = require('../index').server;
 
	if (!io) {
		return callback();
	}
 
	var roomClients = io.sockets.adapter.rooms;
	var socketData = {
		onlineGuestCount: roomClients.online_guests ? roomClients.online_guests.length : 0,
		onlineRegisteredCount: SocketRooms.getOnlineUserCount(io),
		socketCount: Object.keys(io.sockets.sockets).length,
		users: {
			categories: roomClients.categories ? roomClients.categories.length : 0,
			recent: roomClients.recent_topics ? roomClients.recent_topics.length : 0,
			unread: roomClients.unread_topics ? roomClients.unread_topics.length : 0,
			topics: 0,
			category: 0
		},
		topics: {}
	};
 
	var	topTenTopics = [];
	var tid;
 
	for (var room in roomClients) {
		if (roomClients.hasOwnProperty(room)) {
			tid = room.match(/^topic_(\d+)/);
			if (tid) {
				socketData.users.topics += roomClients[room].length;
				topTenTopics.push({tid: tid[1], count: roomClients[room].length});
			} else if (room.match(/^category/)) {
				socketData.users.category += roomClients[room].length;
			}
		}
	}
 
	topTenTopics = topTenTopics.sort(function (a, b) {
		return b.count - a.count;
	}).slice(0, 10);
 
	socketData.topics = topTenTopics;
	callback(null, socketData);
};
 
 
module.exports = SocketRooms;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/social.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/social.js

Statements: 25% (1 / 4)      Branches: 100% (0 / 0)      Functions: 0% (0 / 1)      Lines: 25% (1 / 4)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11    2                
"use strict";
 
var social = require('../../social'),
	SocketSocial = {};
 
SocketSocial.savePostSharingNetworks = function (socket, data, callback) {
	social.setActivePostSharingNetworks(data, callback);
};
 
module.exports = SocketSocial;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/tags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/tags.js

Statements: 7.69% (1 / 13)      Branches: 0% (0 / 4)      Functions: 0% (0 / 3)      Lines: 7.69% (1 / 13)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28    2                                                  
"use strict";
 
var topics = require('../../topics');
var Tags = {};
 
Tags.create = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	topics.createEmptyTag(data.tag, callback);
};
 
Tags.update = function (socket, data, callback) {
	if (!data) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	topics.updateTag(data.tag, data, callback);
};
 
Tags.deleteTags = function (socket, data, callback) {
	topics.deleteTags(data.tags, callback);
};
 
 
module.exports = Tags;
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/user.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/admin/user.js

Statements: 3.17% (4 / 126)      Branches: 0% (0 / 60)      Functions: 0% (0 / 41)      Lines: 3.17% (4 / 126)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258    2 2   2                                                                                                                                                                                                                                                                                                           1                                                                                                                                                                                                            
"use strict";
 
var async = require('async');
var validator = require('validator');
 
var db = require('../../database');
var groups = require('../../groups');
var user = require('../../user');
var events = require('../../events');
var meta = require('../../meta');
 
var User = {};
 
User.makeAdmins = function (socket, uids, callback) {
	if(!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	user.getUsersFields(uids, ['banned'], function (err, userData) {
		if (err) {
			return callback(err);
		}
 
		for(var i = 0; i < userData.length; i++) {
			if (userData[i] && parseInt(userData[i].banned, 10) === 1) {
				return callback(new Error('[[error:cant-make-banned-users-admin]]'));
			}
		}
 
		async.each(uids, function (uid, next) {
			groups.join('administrators', uid, next);
		}, callback);
	});
};
 
User.removeAdmins = function (socket, uids, callback) {
	if(!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.eachSeries(uids, function (uid, next) {
		groups.getMemberCount('administrators', function (err, count) {
			if (err) {
				return next(err);
			}
 
			if (count === 1) {
				return next(new Error('[[error:cant-remove-last-admin]]'));
			}
 
			groups.leave('administrators', uid, next);
		});
	}, callback);
};
 
User.createUser = function (socket, userData, callback) {
	if (!userData) {
		return callback(new Error('[[error:invalid-data]]'));
	}
	user.create(userData, callback);
};
 
User.resetLockouts = function (socket, uids, callback) {
	if (!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.each(uids, user.auth.resetLockout, callback);
};
 
User.resetFlags = function (socket, uids, callback) {
	if (!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	user.resetFlags(uids, callback);
};
 
User.validateEmail = function (socket, uids, callback) {
	if (!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	uids = uids.filter(function (uid) {
		return parseInt(uid, 10);
	});
 
	async.each(uids, function (uid, next) {
		user.setUserField(uid, 'email:confirmed', 1, next);
	}, function (err) {
		if (err) {
			return callback(err);
		}
		db.sortedSetRemove('users:notvalidated', uids, callback);
	});
};
 
User.sendValidationEmail = function (socket, uids, callback) {
	if (!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	if (parseInt(meta.config.requireEmailConfirmation, 10) !== 1) {
		return callback(new Error('[[error:email-confirmations-are-disabled]]'));
	}
 
	user.getUsersFields(uids, ['uid', 'email'], function (err, usersData) {
		if (err) {
			return callback(err);
		}
 
		async.eachLimit(usersData, 50, function (userData, next) {
			if (userData.email && userData.uid) {
				user.email.sendValidationEmail(userData.uid, userData.email, next);
			} else {
				next();
			}
		}, callback);
	});
};
 
User.sendPasswordResetEmail = function (socket, uids, callback) {
	if (!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	uids = uids.filter(function (uid) {
		return parseInt(uid, 10);
	});
 
	async.each(uids, function (uid, next) {
		user.getUserFields(uid, ['email', 'username'], function (err, userData) {
			if (err) {
				return next(err);
			}
			if (!userData.email) {
				return next(new Error('[[error:user-doesnt-have-email, ' + userData.username + ']]'));
			}
			user.reset.send(userData.email, next);
		});
	}, callback);
};
 
User.deleteUsers = function (socket, uids, callback) {
	deleteUsers(socket, uids, function (uid, next) {
		user.deleteAccount(uid, next);
	}, callback);
};
 
User.deleteUsersAndContent = function (socket, uids, callback) {
	deleteUsers(socket, uids, function (uid, next) {
		user.delete(socket.uid, uid, next);
	}, callback);
};
 
function deleteUsers(socket, uids, method, callback) {
	if (!Array.isArray(uids)) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.each(uids, function (uid, next) {
		async.waterfall([
			function (next) {
				user.isAdministrator(uid, next);
			},
			function (isAdmin, next) {
				if (isAdmin) {
					return next(new Error('[[error:cant-delete-other-admins]]'));
				}
 
				method(uid, next);
			},
			function (next) {
				events.log({
					type: 'user-delete',
					uid: socket.uid,
					targetUid: uid,
					ip: socket.ip
				});
				next();
			}
		], next);
	}, callback);
}
 
User.search = function (socket, data, callback) {
	var searchData;
	async.waterfall([
		function (next) {
			user.search({query: data.query, searchBy: data.searchBy, uid: socket.uid}, next);
		},
		function (_searchData, next) {
			searchData = _searchData;
			if (!searchData.users.length) {
				return callback(null, searchData);
			}
 
			var uids = searchData.users.map(function (user) {
				return user && user.uid;
			});
 
			user.getUsersFields(uids, ['email', 'flags', 'lastonline', 'joindate'], next);
		},
		function (userInfo, next) {
			searchData.users.forEach(function (user, index) {
				if (user && userInfo[index]) {
					user.email = validator.escape(String(userInfo[index].email || ''));
					user.flags = userInfo[index].flags || 0;
					user.lastonlineISO = userInfo[index].lastonlineISO;
					user.joindateISO = userInfo[index].joindateISO;
				}
			});
			next(null, searchData);
		}
	], callback);
};
 
User.deleteInvitation = function (socket, data, callback) {
	user.deleteInvitation(data.invitedBy, data.email, callback);
};
 
User.acceptRegistration = function (socket, data, callback) {
	user.acceptRegistration(data.username, function (err, uid) {
		if (err) {
			return callback(err);
		}
		events.log({
			type: 'registration-approved',
			uid: socket.uid,
			ip: socket.ip,
			targetUid: uid,
		});
		callback();
	});
};
 
User.rejectRegistration = function (socket, data, callback) {
	user.rejectRegistration(data.username, function (err) {
		if (err) {
			return callback(err);
		}
		events.log({
			type: 'registration-rejected',
			uid: socket.uid,
			ip: socket.ip,
			username: data.username,
		});
		callback();
	});
};
 
User.restartJobs = function (socket, data, callback) {
	user.startJobs(callback);
};
 
module.exports = User;
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/

Statements: 6.77% (21 / 310)      Branches: 0% (0 / 164)      Functions: 0% (0 / 101)      Lines: 6.77% (21 / 310)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/
File Statements Branches Functions Lines
bookmarks.js 16.67% (1 / 6) 100% (0 / 0) 0% (0 / 3) 16.67% (1 / 6)
edit.js 9.52% (4 / 42) 0% (0 / 33) 0% (0 / 6) 9.52% (4 / 42)
flag.js 3.95% (3 / 76) 0% (0 / 39) 0% (0 / 26) 3.95% (3 / 76)
helpers.js 9.38% (3 / 32) 0% (0 / 23) 0% (0 / 9) 9.38% (3 / 32)
move.js 11.76% (2 / 17) 0% (0 / 9) 0% (0 / 5) 11.76% (2 / 17)
tools.js 6.25% (6 / 96) 0% (0 / 44) 0% (0 / 34) 6.25% (6 / 96)
votes.js 4.88% (2 / 41) 0% (0 / 16) 0% (0 / 18) 4.88% (2 / 41)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/bookmarks.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/bookmarks.js

Statements: 16.67% (1 / 6)      Branches: 100% (0 / 0)      Functions: 0% (0 / 3)      Lines: 16.67% (1 / 6)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17      2                          
'use strict';
 
 
var helpers = require('./helpers');
 
module.exports = function (SocketPosts) {
 
	SocketPosts.bookmark = function (socket, data, callback) {
		helpers.postCommand(socket, 'bookmark', 'bookmarked', '', data, callback);
	};
 
	SocketPosts.unbookmark = function (socket, data, callback) {
		helpers.postCommand(socket, 'unbookmark', 'bookmarked', '', data, callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/edit.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/edit.js

Statements: 9.52% (4 / 42)      Branches: 0% (0 / 33)      Functions: 0% (0 / 6)      Lines: 9.52% (4 / 42)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76    2 2 2   2                                                                                                                                          
'use strict';
 
var async = require('async');
var validator = require('validator');
var _ = require('underscore');
 
var posts = require('../../posts');
var groups = require('../../groups');
var events = require('../../events');
var meta = require('../../meta');
var websockets = require('../index');
 
module.exports = function (SocketPosts) {
 
	SocketPosts.edit = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:not-logged-in]]'));
		} else if (!data || !data.pid || !data.content) {
			return callback(new Error('[[error:invalid-data]]'));
		} else if (data.title && data.title.length < parseInt(meta.config.minimumTitleLength, 10)) {
			return callback(new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]'));
		} else if (data.title && data.title.length > parseInt(meta.config.maximumTitleLength, 10)) {
			return callback(new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]'));
		} else if (data.tags && data.tags.length < parseInt(meta.config.minimumTagsPerTopic, 10)) {
			return callback(new Error('[[error:not-enough-tags, ' + meta.config.minimumTagsPerTopic + ']]'));
		} else if (data.tags && data.tags.length > parseInt(meta.config.maximumTagsPerTopic, 10)) {
			return callback(new Error('[[error:too-many-tags, ' + meta.config.maximumTagsPerTopic + ']]'));
		} else if (!data.content || data.content.length < parseInt(meta.config.minimumPostLength, 10)) {
			return callback(new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]'));
		} else if (data.content.length > parseInt(meta.config.maximumPostLength, 10)) {
			return callback(new Error('[[error:content-too-long, ' + meta.config.maximumPostLength + ']]'));
		}
 
		data.uid = socket.uid;
		data.req = websockets.reqFromSocket(socket);
 
		var editResult;
		async.waterfall([
			function (next) {
				posts.edit(data, next);
			},
			function (result, next) {
				editResult = result;
				if (result.topic.renamed) {
					events.log({
						type: 'topic-rename',
						uid: socket.uid,
						ip: socket.ip,
						oldTitle: validator.escape(String(result.topic.oldTitle)),
						newTitle: validator.escape(String(result.topic.title))
					});
				}
 
				if (parseInt(result.post.deleted) !== 1) {
					websockets.in('topic_' + result.topic.tid).emit('event:post_edited', result);
					return callback(null, result.post);
				}
 
				groups.getMembersOfGroups([
					'administrators',
					'Global Moderators',
					'cid:' + result.topic.cid + ':privileges:mods',
					'cid:' + result.topic.cid + ':privileges:groups:moderate'
				], next);
			},
			function (results, next) {
				var uids = _.unique(_.flatten(results).concat(socket.uid.toString()));
				uids.forEach(function (uid) {
					websockets.in('uid_' + uid).emit('event:post_edited', editResult);
				});
				next(null, editResult.post);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/flag.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/flag.js

Statements: 3.95% (3 / 76)      Branches: 0% (0 / 39)      Functions: 0% (0 / 26)      Lines: 3.95% (3 / 76)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174    2 2   2                                                                                                                                                                                                                                                                                                                                                
'use strict';
 
var async = require('async');
var S = require('string');
 
var user = require('../../user');
var groups = require('../../groups');
var posts = require('../../posts');
var topics = require('../../topics');
var privileges = require('../../privileges');
var notifications = require('../../notifications');
var plugins = require('../../plugins');
var meta = require('../../meta');
var utils = require('../../../public/src/utils');
 
module.exports = function (SocketPosts) {
 
	SocketPosts.flag = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:not-logged-in]]'));
		}
 
		if (!data || !data.pid || !data.reason) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var flaggingUser = {};
		var post;
 
		async.waterfall([
			function (next) {
				posts.getPostFields(data.pid, ['pid', 'tid', 'uid', 'content', 'deleted'], next);
			},
			function (postData, next) {
				if (parseInt(postData.deleted, 10) === 1) {
					return next(new Error('[[error:post-deleted]]'));
				}
 
				post = postData;
				topics.getTopicFields(post.tid, ['title', 'cid'], next);
			},
			function (topicData, next) {
				post.topic = topicData;
 
				async.parallel({
					isAdminOrMod: function (next) {
						privileges.categories.isAdminOrMod(post.topic.cid, socket.uid, next);
					},
					userData: function (next) {
						user.getUserFields(socket.uid, ['username', 'reputation', 'banned'], next);
					}
				}, next);
			},
			function (user, next) {
				var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1;
				if (!user.isAdminOrMod && parseInt(user.userData.reputation, 10) < minimumReputation) {
					return next(new Error('[[error:not-enough-reputation-to-flag]]'));
				}
 
				if (parseInt(user.banned, 10) === 1) {
					return next(new Error('[[error:user-banned]]'));
				}
 
				flaggingUser = user.userData;
				flaggingUser.uid = socket.uid;
 
				posts.flag(post, socket.uid, data.reason, next);
			},
			function (next) {
				async.parallel({
					post: function (next) {
						posts.parsePost(post, next);
					},
					admins: function (next) {
						groups.getMembers('administrators', 0, -1, next);
					},
					globalMods: function (next) {
						groups.getMembers('Global Moderators', 0, -1, next);
					},
					moderators: function (next) {
						groups.getMembers('cid:' + post.topic.cid + ':privileges:mods', 0, -1, next);
					}
				}, next);
			},
			function (results, next) {
				var title = S(post.topic.title).decodeHTMLEntities().s;
				var titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
 
				notifications.create({
					bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + titleEscaped + ']]',
					bodyLong: post.content,
					pid: data.pid,
					path: '/post/' + data.pid,
					nid: 'post_flag:' + data.pid + ':uid:' + socket.uid,
					from: socket.uid,
					mergeId: 'notifications:user_flagged_post_in|' + data.pid,
					topicTitle: post.topic.title
				}, function (err, notification) {
					if (err || !notification) {
						return next(err);
					}
 
					plugins.fireHook('action:post.flag', {post: post, reason: data.reason, flaggingUser: flaggingUser});
					notifications.push(notification, results.admins.concat(results.moderators).concat(results.globalMods), next);
				});
			}
		], callback);
	};
 
	SocketPosts.dismissFlag = function (socket, pid, callback) {
		if (!pid || !socket.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		async.waterfall([
			function (next) {
				user.isAdminOrGlobalMod(socket.uid, next);
			},
			function (isAdminOrGlobalModerator, next) {
				if (!isAdminOrGlobalModerator) {
					return next(new Error('[[no-privileges]]'));
				}
				posts.dismissFlag(pid, next);
			}
		], callback);
	};
 
	SocketPosts.dismissAllFlags = function (socket, data, callback) {
		async.waterfall([
			function (next) {
				user.isAdminOrGlobalMod(socket.uid, next);
			},
			function (isAdminOrGlobalModerator, next) {
				if (!isAdminOrGlobalModerator) {
					return next(new Error('[[no-privileges]]'));
				}
				posts.dismissAllFlags(next);
			}
		], callback);
	};
 
	SocketPosts.updateFlag = function (socket, data, callback) {
		if (!data || !(data.pid && data.data)) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var payload = {};
 
		async.waterfall([
			function (next) {
				async.parallel([
					async.apply(user.isAdminOrGlobalMod, socket.uid),
					async.apply(user.isModeratorOfAnyCategory, socket.uid)
				], function (err, results) {
					next(err, results[0] || results[1]);
				});
			},
			function (allowed, next) {
				if (!allowed) {
					return next(new Error('[[no-privileges]]'));
				}
 
				// Translate form data into object
				payload = data.data.reduce(function (memo, cur) {
					memo[cur.name] = cur.value;
					return memo;
				}, payload);
 
				posts.updateFlagData(socket.uid, data.pid, payload, next);
			}
		], callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/helpers.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/helpers.js

Statements: 9.38% (3 / 32)      Branches: 0% (0 / 23)      Functions: 0% (0 / 9)      Lines: 9.38% (3 / 32)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77      2 2                                                                                                       1                                        
'use strict';
 
 
var async = require('async');
var posts = require('../../posts');
var plugins = require('../../plugins');
var websockets = require('../index');
var socketHelpers = require('../helpers');
 
var helpers = module.exports;
 
helpers.postCommand = function (socket, command, eventName, notification, data, callback) {
	if (!socket.uid) {
		return callback(new Error('[[error:not-logged-in]]'));
	}
 
	if (!data || !data.pid || !data.room_id) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	async.waterfall([
		function (next) {
			async.parallel({
				exists: function (next) {
					posts.exists(data.pid, next);
				},
				deleted: function (next) {
					posts.getPostField(data.pid, 'deleted', next);
				}
			}, next);
		},
		function (results, next) {
			if (!results.exists) {
				return next(new Error('[[error:invalid-pid]]'));
			}
 
			if (parseInt(results.deleted, 10) === 1) {
				return next(new Error('[[error:post-deleted]]'));
			}
 
			/*
			hooks:
				filter:post.upvote
				filter:post.downvote
				filter:post.unvote
				filter:post.bookmark
				filter:post.unbookmark
			 */
			plugins.fireHook('filter:post.' + command, {data: data, uid: socket.uid}, next);
		},
		function (filteredData, next) {
			executeCommand(socket, command, eventName, notification, filteredData.data, next);
		}
	], callback);
};
 
function executeCommand(socket, command, eventName, notification, data, callback) {
	async.waterfall([
		function (next) {
			posts[command](data.pid, socket.uid, next);
		},
		function (result, next) {
			if (result && eventName) {
				websockets.in('uid_' + socket.uid).emit('posts.' + command, result);
				websockets.in(data.room_id).emit('event:' + eventName, result);
			}
 
			if (result && notification) {
				socketHelpers.sendNotificationToPostOwner(data.pid, socket.uid, command, notification);
			} else if (result && command === 'unvote') {
				socketHelpers.rescindUpvoteNotification(data.pid, socket.uid);
			}
			next(null, result);
		}
	], callback);
}
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/move.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/move.js

Statements: 11.76% (2 / 17)      Branches: 0% (0 / 9)      Functions: 0% (0 / 5)      Lines: 11.76% (2 / 17)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38    2 2                                                                    
'use strict';
 
var async = require('async');
var privileges = require('../../privileges');
var topics = require('../../topics');
var socketHelpers = require('../helpers');
 
module.exports = function (SocketPosts) {
 
	SocketPosts.movePost = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:not-logged-in]]'));
		}
 
		if (!data || !data.pid || !data.tid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				privileges.posts.canMove(data.pid, socket.uid, next);
			},
			function (canMove, next) {
				if (!canMove) {
					return next(new Error('[[error:no-privileges]]'));
				}
 
				topics.movePostToTopic(data.pid, data.tid, next);
			},
			function (next) {
				socketHelpers.sendNotificationToPostOwner(data.pid, socket.uid, 'move', 'notifications:moved_your_post');
				next();
			}
		], callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/tools.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/tools.js

Statements: 6.25% (6 / 96)      Branches: 0% (0 / 44)      Functions: 0% (0 / 34)      Lines: 6.25% (6 / 96)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209    2 2   2                                                                                                                                                                                                                                                                       1                                                                                             1                 1                              
'use strict';
 
var async = require('async');
var validator = require('validator');
 
var posts = require('../../posts');
var topics = require('../../topics');
var events = require('../../events');
var websockets = require('../index');
var socketTopics = require('../topics');
var privileges = require('../../privileges');
var plugins = require('../../plugins');
var social = require('../../social');
 
module.exports = function (SocketPosts) {
 
	SocketPosts.loadPostTools = function (socket, data, callback) {
		if (!data) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.parallel({
			posts: function (next) {
				posts.getPostFields(data.pid, ['deleted', 'bookmarks', 'uid'], next);
			},
			isAdminOrMod: function (next) {
				privileges.categories.isAdminOrMod(data.cid, socket.uid, next);
			},
			canEdit: function (next) {
				privileges.posts.canEdit(data.pid, socket.uid, next);
			},
			canDelete: function (next) {
				privileges.posts.canDelete(data.pid, socket.uid, next);
			},
			bookmarked: function (next) {
				posts.hasBookmarked(data.pid, socket.uid, next);
			},
			tools: function (next) {
				plugins.fireHook('filter:post.tools', {pid: data.pid, uid: socket.uid, tools: []}, next);
			},
			postSharing: function (next) {
				social.getActivePostSharing(next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			results.posts.tools = results.tools.tools;
			results.posts.deleted = parseInt(results.posts.deleted, 10) === 1;
			results.posts.bookmarked = results.bookmarked;
			results.posts.selfPost = socket.uid && socket.uid === parseInt(results.posts.uid, 10);
			results.posts.display_edit_tools = results.canEdit.flag;
			results.posts.display_delete_tools = results.canDelete.flag;
			results.posts.display_moderator_tools = results.posts.display_edit_tools || results.posts.display_delete_tools;
			results.posts.display_move_tools = results.isAdminOrMod;
			callback(null, results);
		});
	};
 
	SocketPosts.delete = function (socket, data, callback) {
		if (!data || !data.pid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		var postData;
		async.waterfall([
			function (next) {
				posts.tools.delete(socket.uid, data.pid, next);
			},
			function (_postData, next) {
				postData = _postData;
				isMainAndLastPost(data.pid, next);
			},
			function (results, next) {
				if (results.isMain && results.isLast) {
					deleteTopicOf(data.pid, socket, next);
				} else {
					next();
				}
			},
			function (next) {
				websockets.in('topic_' + data.tid).emit('event:post_deleted', postData);
 
				events.log({
					type: 'post-delete',
					uid: socket.uid,
					pid: data.pid,
					ip: socket.ip
				});
 
				next();
			}
		], callback);
	};
 
	SocketPosts.restore = function (socket, data, callback) {
		if (!data || !data.pid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		posts.tools.restore(socket.uid, data.pid, function (err, postData) {
			if (err) {
				return callback(err);
			}
 
			websockets.in('topic_' + data.tid).emit('event:post_restored', postData);
 
			events.log({
				type: 'post-restore',
				uid: socket.uid,
				pid: data.pid,
				ip: socket.ip
			});
 
			callback();
		});
	};
 
	SocketPosts.deletePosts = function (socket, data, callback) {
		if (!data || !Array.isArray(data.pids)) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		async.each(data.pids, function (pid, next) {
			SocketPosts.delete(socket, {pid: pid, tid: data.tid}, next);
		}, callback);
	};
 
	SocketPosts.purgePosts = function (socket, data, callback) {
		if (!data || !Array.isArray(data.pids)) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		async.each(data.pids, function (pid, next) {
			SocketPosts.purge(socket, {pid: pid, tid: data.tid}, next);
		}, callback);
	};
 
	SocketPosts.purge = function (socket, data, callback) {
		function purgePost() {
			var postData;
			async.waterfall([
				function (next) {
					posts.getPostField(data.pid, 'toPid', next);
				},
				function (toPid, next) {
					postData = {pid: data.pid, toPid: toPid};
					posts.tools.purge(socket.uid, data.pid, next);
				},
				function (next) {
					websockets.in('topic_' + data.tid).emit('event:post_purged', postData);
					topics.getTopicField(data.tid, 'title', next);
				},
				function (title, next) {
					events.log({
						type: 'post-purge',
						uid: socket.uid,
						pid: data.pid,
						ip: socket.ip,
						title: validator.escape(String(title))
					}, next);
				}
			], callback);
		}
 
		if (!data || !parseInt(data.pid, 10)) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		isMainAndLastPost(data.pid, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (!results.isMain) {
				return purgePost();
			}
 
			if (!results.isLast) {
				return callback(new Error('[[error:cant-purge-main-post]]'));
			}
 
			deleteTopicOf(data.pid, socket, callback);
		});
	};
 
	function deleteTopicOf(pid, socket, callback) {
		posts.getTopicFields(pid, ['tid', 'cid'], function (err, topic) {
			if (err) {
				return callback(err);
			}
			socketTopics.doTopicAction('delete', 'event:topic_deleted', socket, {tids: [topic.tid], cid: topic.cid}, callback);
		});
	}
 
	function isMainAndLastPost(pid, callback) {
		async.parallel({
			isMain: function (next) {
				posts.isMain(pid, next);
			},
			isLast: function (next) {
				posts.getTopicFields(pid, ['postcount'], function (err, topic) {
					next(err, topic ? parseInt(topic.postcount, 10) === 1 : false);
				});
			}
		}, callback);
	}
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/votes.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/posts/votes.js

Statements: 4.88% (2 / 41)      Branches: 0% (0 / 16)      Functions: 0% (0 / 18)      Lines: 4.88% (2 / 41)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93    2   2                                                                                                                                                                                
'use strict';
 
var async = require('async');
 
var db = require('../../database');
var user = require('../../user');
var posts = require('../../posts');
var privileges = require('../../privileges');
var helpers = require('./helpers');
 
module.exports = function (SocketPosts) {
 
	SocketPosts.getVoters = function (socket, data, callback) {
		if (!data || !data.pid || !data.cid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				privileges.categories.isAdminOrMod(data.cid, socket.uid, next);
			},
			function (isAdminOrMod, next) {
				if (!isAdminOrMod) {
					return next(new Error('[[error:no-privileges]]'));
				}
 
				async.parallel({
					upvoteUids: function (next) {
						db.getSetMembers('pid:' + data.pid + ':upvote', next);
					},
					downvoteUids: function (next) {
						db.getSetMembers('pid:' + data.pid + ':downvote', next);
					}
				}, next);
			},
			function (results, next) {
				async.parallel({
					upvoters: function (next) {
						user.getUsersFields(results.upvoteUids, ['username', 'userslug', 'picture'], next);
					},
					upvoteCount: function (next) {
						next(null, results.upvoteUids.length);
					},
					downvoters: function (next) {
						user.getUsersFields(results.downvoteUids, ['username', 'userslug', 'picture'], next);
					},
					downvoteCount: function (next) {
						next(null, results.downvoteUids.length);
					}
				}, next);
			}
		], callback);
	};
 
	SocketPosts.getUpvoters = function (socket, pids, callback) {
		if (!Array.isArray(pids)) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		posts.getUpvotedUidsByPids(pids, function (err, data) {
			if (err || !Array.isArray(data) || !data.length) {
				return callback(err, []);
			}
 
			async.map(data, function (uids, next)  {
				var otherCount = 0;
				if (uids.length > 6) {
					otherCount = uids.length - 5;
					uids = uids.slice(0, 5);
				}
				user.getUsernamesByUids(uids, function (err, usernames) {
					next(err, {
						otherCount: otherCount,
						usernames: usernames
					});
				});
			}, callback);
		});
	};
 
	SocketPosts.upvote = function (socket, data, callback) {
		helpers.postCommand(socket, 'upvote', 'voted', 'notifications:upvoted_your_post_in', data, callback);
	};
 
	SocketPosts.downvote = function (socket, data, callback) {
		helpers.postCommand(socket, 'downvote', 'voted', '', data, callback);
	};
 
	SocketPosts.unvote = function (socket, data, callback) {
		helpers.postCommand(socket, 'unvote', 'voted', '', data, callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/

Statements: 4.68% (11 / 235)      Branches: 0% (0 / 115)      Functions: 0% (0 / 75)      Lines: 4.68% (11 / 235)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/
File Statements Branches Functions Lines
infinitescroll.js 3.13% (2 / 64) 0% (0 / 41) 0% (0 / 13) 3.13% (2 / 64)
move.js 5.71% (2 / 35) 0% (0 / 16) 0% (0 / 13) 5.71% (2 / 35)
tags.js 4.76% (1 / 21) 0% (0 / 8) 0% (0 / 6) 4.76% (1 / 21)
tools.js 6.67% (4 / 60) 0% (0 / 24) 0% (0 / 19) 6.67% (4 / 60)
unread.js 3.64% (2 / 55) 0% (0 / 26) 0% (0 / 24) 3.64% (2 / 55)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/infinitescroll.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/infinitescroll.js

Statements: 3.13% (2 / 64)      Branches: 0% (0 / 41)      Functions: 0% (0 / 13)      Lines: 3.13% (2 / 64)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127    2   2                                                                                                                                                                                                                                                    
'use strict';
 
var async = require('async');
 
var topics = require('../../topics');
var privileges = require('../../privileges');
var meta = require('../../meta');
var utils = require('../../../public/src/utils');
var social = require('../../social');
 
module.exports = function (SocketTopics) {
 
	SocketTopics.loadMore = function (socket, data, callback) {
		if (!data || !data.tid || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0)  {
			return callback(new Error('[[error:invalid-data]]'));
		}
		var userPrivileges;
 
		async.waterfall([
			function (next) {
				async.parallel({
					privileges: function (next) {
						privileges.topics.get(data.tid, socket.uid, next);
					},
					topic: function (next) {
						topics.getTopicFields(data.tid, ['postcount', 'deleted'], next);
					}
				}, next);
			},
			function (results, next) {
				if (!results.privileges.read || (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted)) {
					return callback(new Error('[[error:no-privileges]]'));
				}
 
				userPrivileges = results.privileges;
 
				var set = 'tid:' + data.tid + ':posts';
				if (data.topicPostSort === 'most_votes') {
					set = 'tid:' + data.tid + ':posts:votes';
				}
				var reverse = data.topicPostSort === 'newest_to_oldest' || data.topicPostSort === 'most_votes';
				var start = Math.max(0, parseInt(data.after, 10));
 
				var infScrollPostsPerPage = 10;
 
				if (data.direction > 0) {
					if (reverse) {
						start = results.topic.postcount - start;
					}
				} else {
					if (reverse) {
						start = results.topic.postcount - start - infScrollPostsPerPage - 1;
					} else {
						start = start - infScrollPostsPerPage - 1;
					}
				}
 
				var stop = start + (infScrollPostsPerPage - 1);
 
				start = Math.max(0, start);
				stop = Math.max(0, stop);
 
				async.parallel({
					mainPost: function (next) {
						if (start > 0) {
							return next();
						}
						topics.getMainPost(data.tid, socket.uid, next);
					},
					posts: function (next) {
						topics.getTopicPosts(data.tid, set, start, stop, socket.uid, reverse, next);
					},
					postSharing: function (next) {
						social.getActivePostSharing(next);
					}
				}, next);
			},
			function (topicData, next) {
				if (topicData.mainPost) {
					topicData.posts = [topicData.mainPost].concat(topicData.posts);
				}
 
				topicData.privileges = userPrivileges;
				topicData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
				topicData['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
 
				topics.modifyPostsByPrivilege(topicData, userPrivileges);
				next(null, topicData);
			}
		], callback);
	};
 
	SocketTopics.loadMoreUnreadTopics = function (socket, data, callback) {
		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var start = parseInt(data.after, 10);
		var stop = start + 9;
 
		topics.getUnreadTopics(data.cid, socket.uid, start, stop, data.filter, callback);
	};
 
	SocketTopics.loadMoreRecentTopics = function (socket, data, callback) {
		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var start = parseInt(data.after, 10);
		var stop = start + 9;
 
		topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback);
	};
 
	SocketTopics.loadMoreFromSet = function (socket, data, callback) {
		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var start = parseInt(data.after, 10);
		var stop = start + 9;
 
		topics.getTopicsFromSet(data.set, socket.uid, start, stop, callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/move.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/move.js

Statements: 5.71% (2 / 35)      Branches: 0% (0 / 16)      Functions: 0% (0 / 13)      Lines: 5.71% (2 / 35)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75    2 2                                                                                                                                              
'use strict';
 
var async = require('async');
var topics = require('../../topics');
var categories = require('../../categories');
var privileges = require('../../privileges');
var socketHelpers = require('../helpers');
 
module.exports = function (SocketTopics) {
 
	SocketTopics.move = function (socket, data, callback) {
		if (!data || !Array.isArray(data.tids) || !data.cid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.eachLimit(data.tids, 10, function (tid, next) {
			var topicData;
			async.waterfall([
				function (next) {
					privileges.topics.isAdminOrMod(tid, socket.uid, next);
				},
				function (canMove, next) {
					if (!canMove) {
						return next(new Error('[[error:no-privileges]]'));
					}
					next();
				},
				function (next) {
					topics.getTopicFields(tid, ['cid', 'slug'], next);
				},
				function (_topicData, next) {
					topicData = _topicData;
					topicData.tid = tid;
					topics.tools.move(tid, data.cid, socket.uid, next);
				}
			], function (err) {
				if (err) {
					return next(err);
				}
 
				socketHelpers.emitToTopicAndCategory('event:topic_moved', topicData);
 
				socketHelpers.sendNotificationToTopicOwner(tid, socket.uid, 'move', 'notifications:moved_your_topic');
 
				next();
			});
		}, callback);
	};
 
 
	SocketTopics.moveAll = function (socket, data, callback) {
		if (!data || !data.cid || !data.currentCid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				privileges.categories.canMoveAllTopics(data.currentCid, data.cid, socket.uid, next);
			},
			function (canMove, next) {
				if (!canMove) {
					return callback(new Error('[[error:no-privileges]]'));
				}
 
				categories.getAllTopicIds(data.currentCid, 0, -1, next);
			},
			function (tids, next) {
				async.eachLimit(tids, 50, function (tid, next) {
					topics.tools.move(tid, data.cid, socket.uid, next);
				}, next);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/tags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/tags.js

Statements: 4.76% (1 / 21)      Branches: 0% (0 / 8)      Functions: 0% (0 / 6)      Lines: 4.76% (1 / 21)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40    2                                                                          
'use strict';
 
var topics = require('../../topics');
var utils = require('../../../public/src/utils');
 
module.exports = function (SocketTopics) {
	SocketTopics.autocompleteTags = function (socket, data, callback) {
		topics.autocompleteTags(data, callback);
	};
 
	SocketTopics.searchTags = function (socket, data, callback) {
		topics.searchTags(data, callback);
	};
 
	SocketTopics.searchAndLoadTags = function (socket, data, callback) {
		if (!data) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		topics.searchAndLoadTags(data, callback);
	};
 
	SocketTopics.loadMoreTags = function (socket, data, callback) {
		if (!data || !utils.isNumber(data.after)) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var start = parseInt(data.after, 10);
		var stop = start + 99;
 
		topics.getTags(start, stop, function (err, tags) {
			if (err) {
				return callback(err);
			}
			tags = tags.filter(Boolean);
			callback(null, {tags: tags, nextStart: stop + 1});
		});
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/tools.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/tools.js

Statements: 6.67% (4 / 60)      Branches: 0% (0 / 24)      Functions: 0% (0 / 19)      Lines: 6.67% (4 / 60)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120    2 2 2   2                                                                                                                                                                                                                                  
'use strict';
 
var async = require('async');
var winston = require('winston');
var validator = require('validator');
 
var topics = require('../../topics');
var events = require('../../events');
var privileges = require('../../privileges');
var plugins = require('../../plugins');
var socketHelpers = require('../helpers');
 
module.exports = function (SocketTopics) {
 
	SocketTopics.loadTopicTools = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:no-privileges]]'));
		}
		if (!data) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		var topic;
		async.waterfall([
			function (next) {
				async.parallel({
					topic: function (next) {
						topics.getTopicData(data.tid, next);
					},
					privileges: function (next) {
						privileges.topics.get(data.tid, socket.uid, next);
					}
				}, next);
			},
			function (results, next) {
				topic = results.topic;
				topic.privileges = results.privileges;
				plugins.fireHook('filter:topic.thread_tools', {topic: results.topic, uid: socket.uid, tools: []}, next);
			},
			function (data, next) {
				topic.deleted = parseInt(topic.deleted, 10) === 1;
				topic.locked = parseInt(topic.locked, 10) === 1;
				topic.pinned = parseInt(topic.pinned, 10) === 1;
				topic.thread_tools = data.tools;
				next(null, topic);
			}
		], callback);
	};
 
	SocketTopics.delete = function (socket, data, callback) {
		SocketTopics.doTopicAction('delete', 'event:topic_deleted', socket, data, callback);
	};
 
	SocketTopics.restore = function (socket, data, callback) {
		SocketTopics.doTopicAction('restore', 'event:topic_restored', socket, data, callback);
	};
 
	SocketTopics.purge = function (socket, data, callback) {
		SocketTopics.doTopicAction('purge', 'event:topic_purged', socket, data, callback);
	};
 
	SocketTopics.lock = function (socket, data, callback) {
		SocketTopics.doTopicAction('lock', 'event:topic_locked', socket, data, callback);
	};
 
	SocketTopics.unlock = function (socket, data, callback) {
		SocketTopics.doTopicAction('unlock', 'event:topic_unlocked', socket, data, callback);
	};
 
	SocketTopics.pin = function (socket, data, callback) {
		SocketTopics.doTopicAction('pin', 'event:topic_pinned', socket, data, callback);
	};
 
	SocketTopics.unpin = function (socket, data, callback) {
		SocketTopics.doTopicAction('unpin', 'event:topic_unpinned', socket, data, callback);
	};
 
	SocketTopics.doTopicAction = function (action, event, socket, data, callback) {
		callback = callback || function () {};
		if (!socket.uid) {
			return callback(new Error('[[error:no-privileges]]'));
		}
 
		if (!data || !Array.isArray(data.tids) || !data.cid) {
			return callback(new Error('[[error:invalid-tid]]'));
		}
 
		if (typeof topics.tools[action] !== 'function') {
			return callback();
		}
 
		async.each(data.tids, function (tid, next) {
			topics.tools[action](tid, socket.uid, function (err, data) {
				if (err) {
					return next(err);
				}
 
				socketHelpers.emitToTopicAndCategory(event, data);
 
				if (action === 'delete' || action === 'restore' || action === 'purge') {
					topics.getTopicField(tid, 'title', function (err, title) {
						if (err) {
							return winston.error(err);
						}
						events.log({
							type: 'topic-' + action,
							uid: socket.uid,
							ip: socket.ip,
							tid: tid,
							title: validator.escape(String(title))
						});
					});
				}
 
				next();
			});
		}, callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/unread.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/topics/unread.js

Statements: 3.64% (2 / 55)      Branches: 0% (0 / 26)      Functions: 0% (0 / 24)      Lines: 3.64% (2 / 55)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124    2   2                                                                                                                                                                                                                                              
'use strict';
 
var async = require('async');
 
var user = require('../../user');
var topics = require('../../topics');
 
module.exports = function (SocketTopics) {
 
	SocketTopics.markAsRead = function (socket, tids, callback) {
		if (!Array.isArray(tids) || !socket.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		async.waterfall([
			function (next) {
				topics.markAsRead(tids, socket.uid, next);
			},
			function (hasMarked, next) {
				if (hasMarked) {
					topics.pushUnreadCount(socket.uid);
 
					topics.markTopicNotificationsRead(tids, socket.uid);
				}
				next();
			}
		], callback);
	};
 
	SocketTopics.markTopicNotificationsRead = function (socket, tids, callback) {
		if (!Array.isArray(tids) || !socket.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		topics.markTopicNotificationsRead(tids, socket.uid, callback);
	};
 
	SocketTopics.markAllRead = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
		async.waterfall([
			function (next) {
				topics.markAllRead(socket.uid, next);
			},
			function (next) {
				topics.pushUnreadCount(socket.uid);
				next();
			}
		], callback);
	};
 
	SocketTopics.markCategoryTopicsRead = function (socket, cid, callback) {
		async.waterfall([
			function (next) {
				topics.getUnreadTids(cid, socket.uid, '', next);
			},
			function (tids, next) {
				SocketTopics.markAsRead(socket, tids, next);
			}
		], callback);
	};
 
	SocketTopics.markUnread = function (socket, tid, callback) {
		if (!tid || !socket.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		async.waterfall([
			function (next) {
				topics.markUnread(tid, socket.uid, next);
			},
			function (next) {
				topics.pushUnreadCount(socket.uid);
				next();
			}
		], callback);
	};
 
	SocketTopics.markAsUnreadForAll = function (socket, tids, callback) {
		if (!Array.isArray(tids)) {
			return callback(new Error('[[error:invalid-tid]]'));
		}
 
		if (!socket.uid) {
			return callback(new Error('[[error:no-privileges]]'));
		}
 
		async.waterfall([
			function (next) {
				user.isAdministrator(socket.uid, next);
			},
			function (isAdmin, next) {
				async.each(tids, function (tid, next) {
					async.waterfall([
						function (next) {
							topics.exists(tid, next);
						},
						function (exists, next) {
							if (!exists) {
								return next(new Error('[[error:no-topic]]'));
							}
							topics.getTopicField(tid, 'cid', next);
						},
						function (cid, next) {
							user.isModerator(socket.uid, cid, next);
						},
						function (isMod, next) {
							if (!isAdmin && !isMod) {
								return next(new Error('[[error:no-privileges]]'));
							}
							topics.markAsUnreadForAll(tid, next);
						},
						function (next) {
							topics.updateRecent(tid, Date.now(), next);
						}
					], next);
				}, next);
			},
			function (next) {
				topics.pushUnreadCount(socket.uid);
				next();
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/

Statements: 7.28% (15 / 206)      Branches: 0% (0 / 124)      Functions: 0% (0 / 65)      Lines: 7.28% (15 / 206)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/
File Statements Branches Functions Lines
ban.js 11.11% (4 / 36) 0% (0 / 16) 0% (0 / 15) 11.11% (4 / 36)
picture.js 7.69% (4 / 52) 0% (0 / 38) 0% (0 / 17) 7.69% (4 / 52)
profile.js 5.06% (4 / 79) 0% (0 / 54) 0% (0 / 23) 5.06% (4 / 79)
search.js 6.67% (1 / 15) 0% (0 / 8) 0% (0 / 3) 6.67% (1 / 15)
status.js 8.33% (2 / 24) 0% (0 / 8) 0% (0 / 7) 8.33% (2 / 24)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/ban.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/ban.js

Statements: 11.11% (4 / 36)      Branches: 0% (0 / 16)      Functions: 0% (0 / 15)      Lines: 11.11% (4 / 36)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92    2 2                                                                                                   1                                   1                                        
'use strict';
 
var async = require('async');
var user = require('../../user');
var websockets = require('../index');
var events = require('../../events');
 
module.exports = function (SocketUser) {
 
	SocketUser.banUsers = function (socket, data, callback) {
		// Backwards compatibility
		if (Array.isArray(data)) {
			data = {
				uids: data,
				until: 0,
				reason: ''
			};
		}
 
		toggleBan(socket.uid, data.uids, function (uid, next) {
			banUser(uid, data.until || 0, data.reason || '', next);
		}, function (err) {
			if (err) {
				return callback(err);
			}
			async.each(data.uids, function (uid, next) {
				events.log({
					type: 'user-ban',
					uid: socket.uid,
					targetUid: uid,
					ip: socket.ip
				}, next);
			}, callback);
		});
	};
 
	SocketUser.unbanUsers = function (socket, uids, callback) {
		toggleBan(socket.uid, uids, user.unban, function (err) {
			if (err) {
				return callback(err);
			}
 
			async.each(uids, function (uid, next) {
				events.log({
					type: 'user-unban',
					uid: socket.uid,
					targetUid: uid,
					ip: socket.ip
				}, next);
			}, callback);
		});
	};
 
	function toggleBan(uid, uids, method, callback) {
		if (!Array.isArray(uids)) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				user.isAdminOrGlobalMod(uid, next);
			},
			function (isAdminOrGlobalMod, next) {
				if (!isAdminOrGlobalMod) {
					return next(new Error('[[error:no-privileges]]'));
				}
				async.each(uids, method, next);
			}
		], callback);
	}
 
	function banUser(uid, until, reason, callback) {
		async.waterfall([
			function (next) {
				user.isAdministrator(uid, next);
			},
			function (isAdmin, next) {
				if (isAdmin) {
					return next(new Error('[[error:cant-ban-other-admins]]'));
				}
				user.ban(uid, until, reason, next);
			},
			function (next) {
				websockets.in('uid_' + uid).emit('event:banned');
				next();
			}
		], callback);
	}
};
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/picture.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/picture.js

Statements: 7.69% (4 / 52)      Branches: 0% (0 / 38)      Functions: 0% (0 / 17)      Lines: 7.69% (4 / 52)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130    2 2 2   2                                                                                                                                                                                                                                                      
'use strict';
 
var async = require('async');
var winston = require('winston');
var path = require('path');
 
var user = require('../../user');
var plugins = require('../../plugins');
 
module.exports = function (SocketUser) {
 
	SocketUser.changePicture = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		if (!data) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var type = data.type;
 
		async.waterfall([
			function (next) {
				user.isAdminOrSelf(socket.uid, data.uid, next);
			},
			function (next) {
				switch(type) {
					case 'default':
						next(null, '');
						break;
					case 'uploaded':
						user.getUserField(data.uid, 'uploadedpicture', next);
						break;
					default:
						plugins.fireHook('filter:user.getPicture', {
							uid: socket.uid,
							type: type,
							picture: undefined
						}, function (err, returnData) {
							if (err) {
								return next(err);
							}
 
							next(null, returnData.picture || '');
						});
						break;
				}
			},
			function (picture, next) {
				user.setUserField(data.uid, 'picture', picture, next);
			}
		], callback);
	};
 
	SocketUser.uploadProfileImageFromUrl = function (socket, data, callback) {
		if (!socket.uid || !data.url || !data.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		async.waterfall([
			function (next) {
				user.isAdminOrSelf(socket.uid, data.uid, next);
			},
			function (next) {
				user.uploadFromUrl(data.uid, data.url, next);
			},
			function (uploadedImage, next) {
				next(null, uploadedImage ? uploadedImage.url : null);
			}
		], callback);
	};
 
	SocketUser.removeUploadedPicture = function (socket, data, callback) {
		if (!socket.uid || !data.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				user.isAdminOrSelf(socket.uid, data.uid, next);
			},
			function (next) {
				user.getUserFields(data.uid, ['uploadedpicture', 'picture'], next);
			},
			function (userData, next) {
				if (userData.uploadedpicture && !userData.uploadedpicture.startsWith('http')) {
					require('fs').unlink(path.join(__dirname, '../../../public', userData.uploadedpicture), function (err) {
						if (err) {
							winston.error(err);
						}
					});
				}
 
				user.setUserFields(data.uid, {
					uploadedpicture: '',
					picture: userData.uploadedpicture === userData.picture ? '' : userData.picture	// if current picture is uploaded picture, reset to user icon
				}, next);
			}
		], callback);
	};
 
	SocketUser.getProfilePictures = function (socket, data, callback) {
		if (!data || !data.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.parallel({
			list: async.apply(plugins.fireHook, 'filter:user.listPictures', {
				uid: data.uid,
				pictures: []
			}),
			uploaded: async.apply(user.getUserField, data.uid, 'uploadedpicture')
		}, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			if (data.uploaded) {
				data.list.pictures.push({
					type: 'uploaded',
					url: data.uploaded,
					text: '[[user:uploaded_picture]]'
				});
			}
 
			callback(null, data.list.pictures);
		});
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/profile.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/profile.js

Statements: 5.06% (4 / 79)      Branches: 0% (0 / 54)      Functions: 0% (0 / 23)      Lines: 5.06% (4 / 79)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178    2   2                                                                                                     1                                                                                                                                                                                                   1                                                
'use strict';
 
var async = require('async');
 
var user = require('../../user');
var meta = require('../../meta');
var events = require('../../events');
var privileges = require('../../privileges');
 
module.exports = function (SocketUser) {
 
	SocketUser.changeUsernameEmail = function (socket, data, callback) {
		if (!data || !data.uid || !socket.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				isAdminOrSelfAndPasswordMatch(socket.uid, data, next);
			},
			function (next) {
				SocketUser.updateProfile(socket, data, next);
			}
		], callback);
	};
 
	SocketUser.updateCover = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:no-privileges]]'));
		}
		async.waterfall([
			function (next) {
				user.isAdminOrSelf(socket.uid, data.uid, next);
			},
			function (next) {
				user.updateCoverPicture(data, next);
			}
		], callback);
	};
 
	SocketUser.removeCover = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:no-privileges]]'));
		}
 
		async.waterfall([
			function (next) {
				user.isAdminOrSelf(socket.uid, data.uid, next);
			},
			function (next) {
				user.removeCoverPicture(data, next);
			}
		], callback);
	};
 
	function isAdminOrSelfAndPasswordMatch(uid, data, callback) {
		async.parallel({
			isAdmin: async.apply(user.isAdministrator, uid),
			hasPassword: async.apply(user.hasPassword, data.uid),
			passwordMatch: function (next) {
				if (data.password) {
					user.isPasswordCorrect(data.uid, data.password, next);
				} else {
					next(null, false);
				}
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var isSelf = parseInt(uid, 10) === parseInt(data.uid, 10);
 
			if (!results.isAdmin && !isSelf) {
				return callback(new Error('[[error:no-privileges]]'));
			}
 
			if (isSelf && results.hasPassword && !results.passwordMatch) {
				return callback(new Error('[[error:invalid-password]]'));
			}
 
			callback();
		});
	}
 
	SocketUser.changePassword = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		if (!data || !data.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		user.changePassword(socket.uid, data, function (err) {
			if (err) {
				return callback(err);
			}
 
			events.log({
				type: 'password-change',
				uid: socket.uid,
				targetUid: data.uid,
				ip: socket.ip
			});
			callback();
		});
	};
 
	SocketUser.updateProfile = function (socket, data, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		if (!data || !data.uid) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		var oldUserData;
		async.waterfall([
			function (next) {
				user.getUserFields(data.uid, ['email', 'username'], next);
			},
			function (_oldUserData, next) {
				oldUserData = _oldUserData;
				if (!oldUserData || !oldUserData.username) {
					return next(new Error('[[error:invalid-data]]'));
				}
 
				async.parallel({
					isAdminOrGlobalMod: function (next) {
						user.isAdminOrGlobalMod(socket.uid, next);
					},
					canEdit: function (next) {
						privileges.users.canEdit(socket.uid, data.uid, next);
					}
				}, next);
			},
			function (results, next) {
				if (!results.canEdit) {
					return next(new Error('[[error:no-privileges]]'));
				}
 
				if (!results.isAdminOrGlobalMod && parseInt(meta.config['username:disableEdit'], 10) === 1) {
					data.username = oldUserData.username;
				}
 
				if (!results.isAdminOrGlobalMod && parseInt(meta.config['email:disableEdit'], 10) === 1) {
					data.email = oldUserData.email;
				}
 
				user.updateProfile(data.uid, data, next);
			},
			function (userData, next) {
				function log(type, eventData) {
					eventData.type = type;
					eventData.uid = socket.uid;
					eventData.targetUid = data.uid;
					eventData.ip = socket.ip;
 
					events.log(eventData);
				}
 
				if (userData.email !== oldUserData.email) {
					log('email-change', {oldEmail: oldUserData.email, newEmail: userData.email});
				}
 
				if (userData.username !== oldUserData.username) {
					log('username-change', {oldUsername: oldUserData.username, newUsername: userData.username});
				}
 
				next(null, userData);
			}
		], callback);
	};
 
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/search.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/search.js

Statements: 6.67% (1 / 15)      Branches: 0% (0 / 8)      Functions: 0% (0 / 3)      Lines: 6.67% (1 / 15)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36    2                                                                  
'use strict';
 
var user = require('../../user');
var meta = require('../../meta');
var pagination = require('../../pagination');
 
module.exports = function (SocketUser) {
 
	SocketUser.search = function (socket, data, callback) {
		if (!data) {
			return callback(new Error('[[error:invalid-data]]'));
		}
		if (!socket.uid && parseInt(meta.config.allowGuestUserSearching, 10) !== 1) {
			return callback(new Error('[[error:not-logged-in]]'));
		}
		user.search({
			query: data.query,
			page: data.page,
			searchBy: data.searchBy,
			sortBy: data.sortBy,
			onlineOnly: data.onlineOnly,
			bannedOnly: data.bannedOnly,
			flaggedOnly: data.flaggedOnly,
			uid: socket.uid
		}, function (err, result) {
			if (err) {
				return callback(err);
			}
			result.pagination = pagination.create(data.page, result.pageCount);
			result['route_users:' + data.sortBy] = true;
			callback(null, result);
		});
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/status.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/socket.io/user/status.js

Statements: 8.33% (2 / 24)      Branches: 0% (0 / 8)      Functions: 0% (0 / 7)      Lines: 8.33% (2 / 24)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54    2   2                                                                                                  
'use strict';
 
var async = require('async');
 
var user = require('../../user');
var websockets = require('../index');
 
module.exports = function (SocketUser) {
 
	SocketUser.checkStatus = function (socket, uid, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
		async.waterfall([
			function (next) {
				user.getUserFields(uid, ['lastonline', 'status'], next);
			},
			function (userData, next) {
				next(null, user.getStatus(userData));
			}
		], callback);
	};
 
	SocketUser.setStatus = function (socket, status, callback) {
		if (!socket.uid) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		var allowedStatus = ['online', 'offline', 'dnd', 'away'];
		if (allowedStatus.indexOf(status) === -1) {
			return callback(new Error('[[error:invalid-user-status]]'));
		}
 
		var data = {status: status};
		if (status !== 'offline') {
			data.lastonline = Date.now();
		}
 
		async.waterfall([
			function (next) {
				user.setUserFields(socket.uid, data, next);
			},
			function (next) {
				var data = {
					uid: socket.uid,
					status: status
				};
				websockets.server.emit('event:user_status_change', data);
				next(null, data);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/

Statements: 5.55% (81 / 1459)      Branches: 0% (0 / 669)      Functions: 0% (0 / 579)      Lines: 5.57% (81 / 1455)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/topics/
File Statements Branches Functions Lines
create.js 4.32% (7 / 162) 0% (0 / 74) 0% (0 / 55) 4.32% (7 / 162)
data.js 6.12% (3 / 49) 0% (0 / 16) 0% (0 / 16) 6.12% (3 / 49)
delete.js 5.32% (5 / 94) 0% (0 / 20) 0% (0 / 52) 5.32% (5 / 94)
follow.js 9.7% (13 / 134) 0% (0 / 52) 0% (0 / 51) 9.77% (13 / 133)
fork.js 7.14% (6 / 84) 0% (0 / 38) 0% (0 / 33) 7.23% (6 / 83)
popular.js 18.18% (4 / 22) 0% (0 / 8) 0% (0 / 13) 18.18% (4 / 22)
posts.js 2.58% (5 / 194) 0% (0 / 112) 0% (0 / 76) 2.59% (5 / 193)
recent.js 4.29% (3 / 70) 0% (0 / 27) 0% (0 / 29) 4.29% (3 / 70)
suggested.js 18.18% (6 / 33) 0% (0 / 6) 0% (0 / 18) 18.18% (6 / 33)
tags.js 2.97% (6 / 202) 0% (0 / 109) 0% (0 / 84) 2.99% (6 / 201)
teaser.js 4.17% (3 / 72) 0% (0 / 41) 0% (0 / 20) 4.17% (3 / 72)
thumb.js 21.28% (10 / 47) 0% (0 / 18) 0% (0 / 11) 21.28% (10 / 47)
tools.js 4.35% (5 / 115) 0% (0 / 48) 0% (0 / 44) 4.35% (5 / 115)
unread.js 1.78% (3 / 169) 0% (0 / 98) 0% (0 / 73) 1.78% (3 / 169)
user.js 16.67% (2 / 12) 0% (0 / 2) 0% (0 / 4) 16.67% (2 / 12)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/create.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/create.js

Statements: 4.32% (7 / 162)      Branches: 0% (0 / 74)      Functions: 0% (0 / 55)      Lines: 4.32% (7 / 162)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362      2 2 2 2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               1                                                                                                     1                           1                                    
 
'use strict';
 
var async = require('async');
var validator = require('validator');
var S = require('string');
var db = require('../database');
var utils = require('../../public/src/utils');
var plugins = require('../plugins');
var analytics = require('../analytics');
var user = require('../user');
var meta = require('../meta');
var posts = require('../posts');
var privileges = require('../privileges');
var categories = require('../categories');
 
module.exports = function (Topics) {
 
	Topics.create = function (data, callback) {
		// This is an internal method, consider using Topics.post instead
		var timestamp = data.timestamp || Date.now();
		var topicData;
 
		async.waterfall([
			function (next) {
				Topics.resizeAndUploadThumb(data, next);
			},
			function (next) {
				db.incrObjectField('global', 'nextTid', next);
			},
			function (tid, next) {
				topicData = {
					'tid': tid,
					'uid': data.uid,
					'cid': data.cid,
					'mainPid': 0,
					'title': data.title,
					'slug': tid + '/' + (utils.slugify(data.title) || 'topic'),
					'timestamp': timestamp,
					'lastposttime': 0,
					'postcount': 0,
					'viewcount': 0,
					'locked': 0,
					'deleted': 0,
					'pinned': 0
				};
 
				if (data.thumb) {
					topicData.thumb = data.thumb;
				}
 
				plugins.fireHook('filter:topic.create', {topic: topicData, data: data}, next);
			},
			function (data, next) {
				topicData = data.topic;
				db.setObject('topic:' + topicData.tid, topicData, next);
			},
			function (next) {
				async.parallel([
					function (next) {
						db.sortedSetsAdd([
							'topics:tid',
							'cid:' + topicData.cid + ':tids',
							'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids'
						], timestamp, topicData.tid, next);
					},
					function (next) {
						categories.updateRecentTid(topicData.cid, topicData.tid, next);
					},
					function (next) {
						user.addTopicIdToUser(topicData.uid, topicData.tid, timestamp, next);
					},
					function (next) {
						db.incrObjectField('category:' + topicData.cid, 'topic_count', next);
					},
					function (next) {
						db.incrObjectField('global', 'topicCount', next);
					},
					function (next) {
						Topics.createTags(data.tags, topicData.tid, timestamp, next);
					}
				], next);
			},
			function (results, next) {
				plugins.fireHook('action:topic.save', topicData);
				next(null, topicData.tid);
			}
		], callback);
	};
 
	Topics.post = function (data, callback) {
		var uid = data.uid;
		var title = String(data.title).trim();
		data.tags = data.tags || [];
 
		async.waterfall([
			function (next) {
				check(title, meta.config.minimumTitleLength, meta.config.maximumTitleLength, 'title-too-short', 'title-too-long', next);
			},
			function (next) {
				check(data.tags, meta.config.minimumTagsPerTopic, meta.config.maximumTagsPerTopic, 'not-enough-tags', 'too-many-tags', next);
			},
			function (next) {
				if (data.content) {
					data.content = data.content.rtrim();
				}
				check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next);
			},
			function (next) {
				categories.exists(data.cid, next);
			},
			function (categoryExists, next) {
				if (!categoryExists) {
					return next(new Error('[[error:no-category]]'));
				}
				privileges.categories.can('topics:create', data.cid, data.uid, next);
			},
			function (canCreate, next) {
				if (!canCreate) {
					return next(new Error('[[error:no-privileges]]'));
				}
				guestHandleValid(data, next);
			},
			function (next) {
				user.isReadyToPost(data.uid, data.cid, next);
			},
			function (next) {
				plugins.fireHook('filter:topic.post', data, next);
			},
			function (filteredData, next) {
				data = filteredData;
				Topics.create(data, next);
			},
			function (tid, next) {
				var postData = data;
				postData.tid = tid;
				postData.ip = data.req ? data.req.ip : null;
				postData.isMain = true;
				posts.create(postData, next);
			},
			function (postData, next) {
				onNewPost(postData, data, next);
			},
			function (postData, next) {
				async.parallel({
					postData: function (next) {
						next(null, postData);
					},
					settings: function (next) {
						user.getSettings(uid, function (err, settings) {
							if (err) {
								return next(err);
							}
							if (settings.followTopicsOnCreate) {
								Topics.follow(postData.tid, uid, next);
							} else {
								next();
							}
						});
					},
					topicData: function (next) {
						Topics.getTopicsByTids([postData.tid], uid, next);
					}
				}, next);
			},
			function (data, next) {
				if (!Array.isArray(data.topicData) || !data.topicData.length) {
					return next(new Error('[[error:no-topic]]'));
				}
 
				data.topicData = data.topicData[0];
				data.topicData.unreplied = 1;
				data.topicData.mainPost = data.postData;
				data.postData.index = 0;
 
				analytics.increment(['topics', 'topics:byCid:' + data.topicData.cid]);
				plugins.fireHook('action:topic.post', data.topicData);
 
				if (parseInt(uid, 10)) {
					user.notifications.sendTopicNotificationToFollowers(uid, data.topicData, data.postData);
				}
 
				next(null, {
					topicData: data.topicData,
					postData: data.postData
				});
			}
		], callback);
	};
 
	Topics.reply = function (data, callback) {
		var tid = data.tid;
		var uid = data.uid;
		var content = data.content;
		var postData;
		var cid;
 
		async.waterfall([
			function (next) {
				Topics.getTopicField(tid, 'cid', next);
			},
			function (_cid, next) {
				cid = _cid;
				async.parallel({
					topicData: async.apply(Topics.getTopicData, tid),
					canReply: async.apply(privileges.topics.can, 'topics:reply', tid, uid),
					isAdminOrMod: async.apply(privileges.categories.isAdminOrMod, cid, uid),
				}, next);
			},
			function (results, next) {
				if (!results.topicData) {
					return next(new Error('[[error:no-topic]]'));
				}
 
				if (parseInt(results.topicData.locked, 10) === 1 && !results.isAdminOrMod) {
					return next(new Error('[[error:topic-locked]]'));
				}
 
				if (parseInt(results.topicData.deleted, 10) === 1 && !results.isAdminOrMod) {
					return next(new Error('[[error:topic-deleted]]'));
				}
 
				if (!results.canReply) {
					return next(new Error('[[error:no-privileges]]'));
				}
 
				guestHandleValid(data, next);
			},
			function (next) {
				user.isReadyToPost(uid, cid, next);
			},
			function (next) {
				plugins.fireHook('filter:topic.reply', data, next);
			},
			function (filteredData, next) {
				content = filteredData.content || data.content;
				if (content) {
					content = content.rtrim();
				}
 
				check(content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long', next);
			},
			function (next) {
				posts.create({
					uid: uid,
					tid: tid,
					handle: data.handle,
					content: content,
					toPid: data.toPid,
					timestamp: data.timestamp,
					ip: data.req ? data.req.ip : null
				}, next);
			},
			function (_postData, next) {
				postData = _postData;
				onNewPost(postData, data, next);
			},
			function (postData, next) {
				user.getSettings(uid, next);
			},
			function (settings, next) {
				if (settings.followTopicsOnReply) {
					Topics.follow(postData.tid, uid);
				}
 
				if (parseInt(uid, 10)) {
					user.setUserField(uid, 'lastonline', Date.now());
				}
 
				Topics.notifyFollowers(postData, uid);
				analytics.increment(['posts', 'posts:byCid:' + cid]);
				plugins.fireHook('action:topic.reply', postData);
 
				next(null, postData);
			}
		], callback);
	};
 
	function onNewPost(postData, data, callback) {
		var tid = postData.tid;
		var uid = postData.uid;
		async.waterfall([
			function (next) {
				Topics.markAsUnreadForAll(tid, next);
			},
			function (next) {
				Topics.markAsRead([tid], uid, next);
			},
			function (markedRead, next) {
				async.parallel({
					userInfo: function (next) {
						posts.getUserInfoForPosts([postData.uid], uid, next);
					},
					topicInfo: function (next) {
						Topics.getTopicFields(tid, ['tid', 'title', 'slug', 'cid', 'postcount', 'mainPid'], next);
					},
					parents: function (next) {
						Topics.addParentPosts([postData], next);
					},
					content: function (next) {
						posts.parsePost(postData, next);
					}
				}, next);
			},
			function (results, next) {
				postData.user = results.userInfo[0];
				postData.topic = results.topicInfo;
				postData.index = parseInt(results.topicInfo.postcount, 10) - 1;
 
				// Username override for guests, if enabled
				if (parseInt(meta.config.allowGuestHandles, 10) === 1 && parseInt(postData.uid, 10) === 0 && data.handle) {
					postData.user.username = validator.escape(String(data.handle));
				}
 
				postData.votes = 0;
				postData.bookmarked = false;
				postData.display_edit_tools = true;
				postData.display_delete_tools = true;
				postData.display_moderator_tools = true;
				postData.display_move_tools = true;
				postData.selfPost = false;
				postData.timestampISO = utils.toISOString(postData.timestamp);
				postData.topic.title = validator.escape(String(postData.topic.title));
 
				next(null, postData);
			}
		], callback);
	}
 
	function check(item, min, max, minError, maxError, callback) {
		// Trim and remove HTML (latter for composers that send in HTML, like redactor)
		if (typeof item === 'string') {
			item = S(item.trim()).stripTags().s;
		}
 
		if (!item || item.length < parseInt(min, 10)) {
			return callback(new Error('[[error:' + minError + ', ' + min + ']]'));
		} else if (item.length > parseInt(max, 10)) {
			return callback(new Error('[[error:' + maxError + ', ' + max + ']]'));
		}
		callback();
	}
 
	function guestHandleValid(data, callback) {
		if (parseInt(meta.config.allowGuestHandles, 10) === 1 && parseInt(data.uid, 10) === 0 && data.handle) {
			if (data.handle.length > meta.config.maximumUsernameLength) {
				return callback(new Error('[[error:guest-handle-invalid]]'));
			}
			user.existsBySlug(utils.slugify(data.handle), function (err, exists) {
				if (err || exists) {
					return callback(err || new Error('[[error:username-taken]]'));
				}
				callback();
			});
			return;
		}
		callback();
	}
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/data.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/data.js

Statements: 6.12% (3 / 49)      Branches: 0% (0 / 16)      Functions: 0% (0 / 16)      Lines: 6.12% (3 / 49)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92    2   2                                                                                                       1                                                                      
'use strict';
 
var validator = require('validator');
 
var db = require('../database');
var categories = require('../categories');
var utils = require('../../public/src/utils');
 
module.exports = function (Topics) {
 
	Topics.getTopicField = function (tid, field, callback) {
		db.getObjectField('topic:' + tid, field, callback);
	};
 
	Topics.getTopicFields = function (tid, fields, callback) {
		db.getObjectFields('topic:' + tid, fields, callback);
	};
 
	Topics.getTopicsFields = function (tids, fields, callback) {
		if (!Array.isArray(tids) || !tids.length) {
			return callback(null, []);
		}
		var keys = tids.map(function (tid) {
			return 'topic:' + tid;
		});
		db.getObjectsFields(keys, fields, callback);
	};
 
	Topics.getTopicData = function (tid, callback) {
		db.getObject('topic:' + tid, function (err, topic) {
			if (err || !topic) {
				return callback(err);
			}
 
			modifyTopic(topic);
			callback(null, topic);
		});
	};
 
	Topics.getTopicsData = function (tids, callback) {
		var keys = [];
 
		for (var i = 0; i < tids.length; ++i) {
			keys.push('topic:' + tids[i]);
		}
 
		db.getObjects(keys, function (err, topics) {
			if (err) {
				return callback(err);
			}
 
			topics.forEach(modifyTopic);
			callback(null, topics);
		});
	};
 
	function modifyTopic(topic) {
		if (!topic) {
			return;
		}
		topic.titleRaw = topic.title;
		topic.title = validator.escape(String(topic.title));
		topic.timestampISO = utils.toISOString(topic.timestamp);
		topic.lastposttimeISO = utils.toISOString(topic.lastposttime);
	}
 
	Topics.getCategoryData = function (tid, callback) {
		Topics.getTopicField(tid, 'cid', function (err, cid) {
			if (err) {
				return callback(err);
			}
 
			categories.getCategoryData(cid, callback);
		});
	};
 
	Topics.setTopicField = function (tid, field, value, callback) {
		db.setObjectField('topic:' + tid, field, value, callback);
	};
 
 
	Topics.setTopicFields = function (tid, data, callback) {
		callback = callback || function () {};
		db.setObject('topic:' + tid, data, callback);
	};
 
	Topics.deleteTopicField = function (tid, field, callback) {
		db.deleteObjectField('topic:' + tid, field, callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/delete.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/delete.js

Statements: 5.32% (5 / 94)      Branches: 0% (0 / 20)      Functions: 0% (0 / 52)      Lines: 5.32% (5 / 94)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225    2 2                                                                                                                                                                                                                                                                                                     1                                       1                                           1                                                                
'use strict';
 
var async = require('async');
var db = require('../database');
 
var user = require('../user');
var posts = require('../posts');
var plugins = require('../plugins');
var batch = require('../batch');
 
 
module.exports = function (Topics) {
 
	Topics.delete = function (tid, uid, callback) {
		Topics.getTopicFields(tid, ['cid'], function (err, topicData) {
			if (err) {
				return callback(err);
			}
 
			async.parallel([
				function (next) {
					Topics.setTopicField(tid, 'deleted', 1, next);
				},
				function (next) {
					db.sortedSetsRemove(['topics:recent', 'topics:posts', 'topics:views'], tid, next);
				},
				function (next) {
					Topics.getPids(tid, function (err, pids) {
						if (err) {
							return next(err);
						}
						db.sortedSetRemove('cid:' + topicData.cid + ':pids', pids, next);
					});
				}
			], function (err) {
				callback(err);
			});
		});
	};
 
	Topics.restore = function (tid, uid, callback) {
		Topics.getTopicFields(tid, ['cid', 'lastposttime', 'postcount', 'viewcount'], function (err, topicData) {
			if (err) {
				return callback(err);
			}
 
			async.parallel([
				function (next) {
					Topics.setTopicField(tid, 'deleted', 0, next);
				},
				function (next) {
					Topics.updateRecent(tid, topicData.lastposttime, next);
				},
				function (next) {
					db.sortedSetAdd('topics:posts', topicData.postcount, tid, next);
				},
				function (next) {
					db.sortedSetAdd('topics:views', topicData.viewcount, tid, next);
				},
				function (next) {
					Topics.getPids(tid, function (err, pids) {
						if (err) {
							return callback(err);
						}
 
						posts.getPostsFields(pids, ['pid', 'timestamp', 'deleted'], function (err, postData) {
							if (err) {
								return next(err);
							}
							postData = postData.filter(function (post) {
								return post && parseInt(post.deleted, 10) !== 1;
							});
							var pidsToAdd = [], scores = [];
							postData.forEach(function (post) {
								pidsToAdd.push(post.pid);
								scores.push(post.timestamp);
							});
							db.sortedSetAdd('cid:' + topicData.cid + ':pids', scores, pidsToAdd, next);
						});
					});
				}
			], function (err) {
				callback(err);
			});
		});
	};
 
	Topics.purgePostsAndTopic = function (tid, uid, callback) {
		var mainPid;
		async.waterfall([
			function (next) {
				Topics.getTopicField(tid, 'mainPid', next);
			},
			function (_mainPid, next) {
				mainPid = _mainPid;
				batch.processSortedSet('tid:' + tid + ':posts', function (pids, next) {
					async.eachLimit(pids, 10, function (pid, next) {
						posts.purge(pid, uid, next);
					}, next);
				}, {alwaysStartAt: 0}, next);
			},
			function (next) {
				posts.purge(mainPid, uid, next);
			},
			function (next) {
				Topics.purge(tid, uid, next);
			}
		], callback);
	};
 
	Topics.purge = function (tid, uid, callback) {
		async.waterfall([
			function (next) {
				deleteFromFollowersIgnorers(tid, next);
			},
			function (next) {
				async.parallel([
					function (next) {
						db.deleteAll([
							'tid:' + tid + ':followers',
							'tid:' + tid + ':ignorers',
							'tid:' + tid + ':posts',
							'tid:' + tid + ':posts:votes',
							'tid:' + tid + ':bookmarks',
							'tid:' + tid + ':posters'
						], next);
					},
					function (next) {
						db.sortedSetsRemove(['topics:tid', 'topics:recent', 'topics:posts', 'topics:views'], tid, next);
					},
					function (next) {
						deleteTopicFromCategoryAndUser(tid, next);
					},
					function (next) {
						Topics.deleteTopicTags(tid, next);
					},
					function (next) {
						reduceCounters(tid, next);
					}
				], next);
			}
		], function (err) {
			if (err) {
				return callback(err);
			}
			plugins.fireHook('action:topic.purge', tid);
			db.delete('topic:' + tid, callback);
		});
	};
 
	function deleteFromFollowersIgnorers(tid, callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					followers: async.apply(db.getSetMembers, 'tid:' + tid + ':followers'),
					ignorers: async.apply(db.getSetMembers, 'tid:' + tid + ':ignorers')
				}, next);
			},
			function (results, next) {
				var followerKeys = results.followers.map(function (uid) {
					return 'uid:' + uid + ':followed_tids';
				});
				var ignorerKeys = results.ignorers.map(function (uid) {
					return 'uid:' + uid + 'ignored_tids';
				});
				db.sortedSetsRemove(followerKeys.concat(ignorerKeys), tid, next);
			}
		], callback);
	}
 
	function deleteTopicFromCategoryAndUser(tid, callback) {
		Topics.getTopicFields(tid, ['cid', 'uid'], function (err, topicData) {
			if (err) {
				return callback(err);
			}
			async.parallel([
				function (next) {
					db.sortedSetsRemove([
						'cid:' + topicData.cid + ':tids',
						'cid:' + topicData.cid + ':tids:pinned',
						'cid:' + topicData.cid + ':tids:posts',
						'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids',
						'uid:' + topicData.uid + ':topics'
					], tid, next);
				},
				function (next) {
					user.decrementUserFieldBy(topicData.uid, 'topiccount', 1, next);
				}
			], callback);
		});
	}
 
	function reduceCounters(tid, callback) {
		var incr = -1;
		async.parallel([
			function (next) {
				db.incrObjectFieldBy('global', 'topicCount', incr, next);
			},
			function (next) {
				Topics.getTopicFields(tid, ['cid', 'postcount'], function (err, topicData) {
					if (err) {
						return next(err);
					}
					topicData.postcount = parseInt(topicData.postcount, 10);
					topicData.postcount = topicData.postcount || 0;
					var postCountChange = incr * topicData.postcount;
 
					async.parallel([
						function (next) {
							db.incrObjectFieldBy('global', 'postCount', postCountChange, next);
						},
						function (next) {
							db.incrObjectFieldBy('category:' + topicData.cid, 'post_count', postCountChange, next);
						},
						function (next) {
							db.incrObjectFieldBy('category:' + topicData.cid, 'topic_count', incr, next);
						}
					], next);
				});
			}
		], callback);
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/follow.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/follow.js

Statements: 9.7% (13 / 134)      Branches: 0% (0 / 52)      Functions: 0% (0 / 51)      Lines: 9.77% (13 / 133)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283      2 2 2 2   2                                                                                                   1                                                 1       1       1       1       1                     1                                     1                                                                                                                                                                                                                                                                                                                  
 
'use strict';
 
var async = require('async');
var nconf = require('nconf');
var S = require('string');
var winston = require('winston');
 
var db = require('../database');
var user = require('../user');
var posts = require('../posts');
var notifications = require('../notifications');
var privileges = require('../privileges');
var meta = require('../meta');
var emailer = require('../emailer');
var plugins = require('../plugins');
 
module.exports = function (Topics) {
 
	Topics.toggleFollow = function (tid, uid, callback) {
		callback = callback || function () {};
		var isFollowing;
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-topic]]'));
				}
				Topics.isFollowing([tid], uid, next);
			},
			function (_isFollowing, next) {
				isFollowing = _isFollowing[0];
				if (isFollowing) {
					Topics.unfollow(tid, uid, next);
				} else {
					Topics.follow(tid, uid, next);
				}
			},
			function (next) {
				next(null, !isFollowing);
			}
		], callback);
	};
 
	Topics.follow = function (tid, uid, callback) {
		setWatching(follow, unignore, 'action:topic.follow', tid, uid, callback);
	};
 
	Topics.unfollow = function (tid, uid, callback) {
		setWatching(unfollow, unignore, 'action:topic.unfollow', tid, uid, callback);
	};
 
	Topics.ignore = function (tid, uid, callback) {
		setWatching(ignore, unfollow, 'action:topic.ignore', tid, uid, callback);
	};
 
	function setWatching(method1, method2, hook, tid, uid, callback) {
		callback = callback || function () {};
		if (!parseInt(uid, 10)) {
			return callback();
		}
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-topic]]'));
				}
				method1(tid, uid, next);
			},
			function (next) {
				method2(tid, uid, next);
			},
			function (next) {
				plugins.fireHook(hook, {uid: uid, tid: tid});
				next();
			}
		], callback);
	}
 
	function follow(tid, uid, callback) {
		addToSets('tid:' + tid + ':followers', 'uid:' + uid + ':followed_tids', tid, uid, callback);
	}
 
	function unfollow(tid, uid, callback) {
		removeFromSets('tid:' + tid + ':followers', 'uid:' + uid + ':followed_tids', tid, uid, callback);
	}
 
	function ignore(tid, uid, callback) {
		addToSets('tid:' + tid + ':ignorers', 'uid:' + uid + ':ignored_tids', tid, uid, callback);
	}
 
	function unignore(tid, uid, callback) {
		removeFromSets('tid:' + tid + ':ignorers', 'uid:' + uid + ':ignored_tids', tid, uid, callback);
	}
 
	function addToSets(set1, set2, tid, uid, callback) {
		async.waterfall([
			function (next) {
				db.setAdd(set1, uid, next);
			},
			function (next) {
				db.sortedSetAdd(set2, Date.now(), tid, next);
			}
		], callback);
	}
 
	function removeFromSets(set1, set2, tid, uid, callback) {
		async.waterfall([
			function (next) {
				db.setRemove(set1, uid, next);
			},
			function (next) {
				db.sortedSetRemove(set2, tid, next);
			}
		], callback);
	}
 
	Topics.isFollowing = function (tids, uid, callback) {
		isIgnoringOrFollowing('followers', tids, uid, callback);
	};
 
	Topics.isIgnoring = function (tids, uid, callback) {
		isIgnoringOrFollowing('ignorers', tids, uid, callback);
	};
 
	function isIgnoringOrFollowing(set, tids, uid, callback) {
		if (!Array.isArray(tids)) {
			return callback();
		}
		if (!parseInt(uid, 10)) {
			return callback(null, tids.map(function () { return false; }));
		}
		var keys = tids.map(function (tid) {
			return 'tid:' + tid + ':' + set;
		});
		db.isMemberOfSets(keys, uid, callback);
	}
 
	Topics.getFollowers = function (tid, callback) {
		db.getSetMembers('tid:' + tid + ':followers', callback);
	};
 
	Topics.getIgnorers = function (tid, callback) {
		db.getSetMembers('tid:' + tid + ':ignorers', callback);
	};
 
	Topics.filterIgnoringUids = function (tid, uids, callback) {
		async.waterfall([
			function (next) {
				db.isSetMembers('tid:' + tid + ':ignorers', uids, next);
			},
			function (isMembers, next) {
				var readingUids = uids.filter(function (uid, index) {
					return uid && isMembers[index];
				});
				next(null, readingUids);
			}
		], callback);
	};
 
	Topics.filterWatchedTids = function (tids, uid, callback) {
		db.sortedSetScores('uid:' + uid + ':followed_tids', tids, function (err, scores) {
			if (err) {
				return callback(err);
			}
			tids = tids.filter(function (tid, index) {
				return tid && !!scores[index];
			});
			callback(null, tids);
		});
	};
 
	Topics.filterNotIgnoredTids = function (tids, uid, callback) {
		db.sortedSetScores('uid:' + uid + ':ignored_tids', tids, function (err, scores) {
			if (err) {
				return callback(err);
			}
			tids = tids.filter(function (tid, index) {
				return tid && !scores[index];
			});
			callback(null, tids);
		});
	};
 
	Topics.notifyFollowers = function (postData, exceptUid, callback) {
		callback = callback || function () {};
		var followers;
		var title;
		var titleEscaped;
 
		async.waterfall([
			function (next) {
				Topics.getFollowers(postData.topic.tid, next);
			},
			function (followers, next) {
				if (!Array.isArray(followers) || !followers.length) {
					return callback();
				}
				var index = followers.indexOf(exceptUid.toString());
				if (index !== -1) {
					followers.splice(index, 1);
				}
				if (!followers.length) {
					return callback();
				}
 
				privileges.topics.filterUids('read', postData.topic.tid, followers, next);
			},
			function (_followers, next) {
				followers = _followers;
				if (!followers.length) {
					return callback();
				}
				title = postData.topic.title;
 
				if (title) {
					title = S(title).decodeHTMLEntities().s;
					titleEscaped = title.replace(/%/g, '&#37;').replace(/,/g, '&#44;');
				}
 
				postData.content = posts.relativeToAbsolute(postData.content);
 
				notifications.create({
					bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
					bodyLong: postData.content,
					pid: postData.pid,
					path: '/post/' + postData.pid,
					nid: 'new_post:tid:' + postData.topic.tid + ':pid:' + postData.pid + ':uid:' + exceptUid,
					tid: postData.topic.tid,
					from: exceptUid,
					mergeId: 'notifications:user_posted_to|' + postData.topic.tid,
					topicTitle: title
				}, next);
			},
			function (notification, next) {
				if (notification) {
					notifications.push(notification, followers);
				}
 
				if (parseInt(meta.config.disableEmailSubscriptions, 10) === 1) {
					return next();
				}
 
				async.eachLimit(followers, 3, function (toUid, next) {
					async.parallel({
						userData: async.apply(user.getUserFields, toUid, ['username', 'userslug']),
						userSettings: async.apply(user.getSettings, toUid)
					}, function (err, data) {
						if (err) {
							return next(err);
						}
 
						if (data.userSettings.sendPostNotifications) {
							emailer.send('notif_post', toUid, {
								pid: postData.pid,
								subject: '[' + (meta.config.title || 'NodeBB') + '] ' + title,
								intro: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
								postBody: postData.content.replace(/"\/\//g, '"https://'),
								site_title: meta.config.title || 'NodeBB',
								username: data.userData.username,
								userslug: data.userData.userslug,
								url: nconf.get('url') + '/topic/' + postData.topic.tid,
								topicSlug: postData.topic.slug,
								postCount: postData.topic.postcount,
								base_url: nconf.get('url')
							}, next);
						} else {
							winston.debug('[topics.notifyFollowers] uid ' + toUid + ' does not have post notifications enabled, skipping.');
							next();
						}
					});
				});
				next();
			}
		], callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/fork.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/fork.js

Statements: 7.14% (6 / 84)      Branches: 0% (0 / 38)      Functions: 0% (0 / 33)      Lines: 7.23% (6 / 83)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181      2 2 2                                                                                                               1                                                                                                                                                                 1                                   1                                        
 
'use strict';
 
var async = require('async');
var winston = require('winston');
var db = require('../database');
var user = require('../user');
var posts = require('../posts');
var privileges = require('../privileges');
var plugins = require('../plugins');
var meta = require('../meta');
 
 
module.exports = function (Topics) {
 
	Topics.createTopicFromPosts = function (uid, title, pids, fromTid, callback) {
		if (title) {
			title = title.trim();
		}
 
		if (title.length < parseInt(meta.config.minimumTitleLength, 10)) {
			return callback(new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]'));
		} else if (title.length > parseInt(meta.config.maximumTitleLength, 10)) {
			return callback(new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]'));
		}
 
		if (!pids || !pids.length) {
			return callback(new Error('[[error:invalid-pid]]'));
		}
 
		pids.sort(function (a, b) {
			return a - b;
		});
		var mainPid = pids[0];
		var cid;
		var tid;
		async.waterfall([
			function (next) {
				posts.getCidByPid(mainPid, next);
			},
			function (_cid, next) {
				cid = _cid;
				async.parallel({
					postData: function (next) {
						posts.getPostData(mainPid, next);
					},
					isAdminOrMod: function (next) {
						privileges.categories.isAdminOrMod(cid, uid, next);
					}
				}, next);
			},
			function (results, next) {
				if (!results.isAdminOrMod) {
					return next(new Error('[[error:no-privileges]]'));
				}
				Topics.create({uid: results.postData.uid, title: title, cid: cid}, next);
			},
			function (results, next) {
				Topics.updateTopicBookmarks(fromTid, pids, function () { next( null, results );} );
			},
			function (_tid, next) {
				function move(pid, next) {
					privileges.posts.canEdit(pid, uid, function (err, canEdit) {
						if (err || !canEdit.flag) {
							return next(err || new Error(canEdit.message));
						}
 
						Topics.movePostToTopic(pid, tid, next);
					});
				}
				tid = _tid;
				async.eachSeries(pids, move, next);
			},
			function (next) {
				Topics.updateTimestamp(tid, Date.now(), next);
			},
			function (next) {
				Topics.getTopicData(tid, next);
			}
		], callback);
	};
 
	Topics.movePostToTopic = function (pid, tid, callback) {
		var postData;
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-topic]]'));
				}
				posts.getPostFields(pid, ['tid', 'uid', 'timestamp', 'upvotes', 'downvotes'], next);
			},
			function (post, next) {
				if (!post || !post.tid) {
					return next(new Error('[[error:no-post]]'));
				}
 
				if (parseInt(post.tid, 10) === parseInt(tid, 10)) {
					return next(new Error('[[error:cant-move-to-same-topic]]'));
				}
 
				postData = post;
				postData.pid = pid;
 
				Topics.removePostFromTopic(postData.tid, postData, next);
			},
			function (next) {
				async.parallel([
					function (next) {
						updateCategoryPostCount(postData.tid, tid, next);
					},
					function (next) {
						Topics.decreasePostCount(postData.tid, next);
					},
					function (next) {
						Topics.increasePostCount(tid, next);
					},
					function (next) {
						posts.setPostField(pid, 'tid', tid, next);
					},
					function (next) {
						Topics.addPostToTopic(tid, postData, next);
					}
				], next);
			},
			function (results, next) {
				async.parallel([
					async.apply(updateRecentTopic, tid),
					async.apply(updateRecentTopic, postData.tid)
				], next);
			}
		], function (err) {
			if (err) {
				return callback(err);
			}
			plugins.fireHook('action:post.move', {post: postData, tid: tid});
			callback();
		});
	};
 
	function updateCategoryPostCount(oldTid, tid, callback) {
		Topics.getTopicsFields([oldTid, tid], ['cid'], function (err, topicData) {
			if (err) {
				return callback(err);
			}
			if (!topicData[0].cid || !topicData[1].cid) {
				return callback();
			}
			if (parseInt(topicData[0].cid, 10) === parseInt(topicData[1].cid, 10)) {
				return callback();
			}
			async.parallel([
				async.apply(db.incrObjectFieldBy, 'category:' + topicData[0].cid, 'post_count', -1),
				async.apply(db.incrObjectFieldBy, 'category:' + topicData[1].cid, 'post_count', 1)
			], callback);
		});
	}
 
	function updateRecentTopic(tid, callback) {
		async.waterfall([
			function (next) {
				Topics.getLatestUndeletedPid(tid, next);
			},
			function (pid, next) {
				if (!pid) {
					return callback();
				}
				posts.getPostField(pid, 'timestamp', next);
			},
			function (timestamp, next) {
				Topics.updateTimestamp(tid, timestamp, next);
			}
		], callback);
	}
 
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/popular.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/popular.js

Statements: 18.18% (4 / 22)      Branches: 0% (0 / 8)      Functions: 0% (0 / 13)      Lines: 18.18% (4 / 22)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54      2 2                                         1           1                                            
 
'use strict';
 
var async = require('async');
var privileges = require('../privileges');
 
module.exports = function (Topics) {
 
	Topics.getPopular = function (term, uid, count, callback) {
		count = parseInt(count, 10) || 20;
 
		if (term === 'alltime') {
			return getAllTimePopular(uid, count, callback);
		}
 
		async.waterfall([
			function (next) {
				Topics.getLatestTidsFromSet('topics:tid', 0, -1, term, next);
			},
			function (tids, next) {
				getTopics(tids, uid, count, next);
			}
		], callback);
	};
 
	function getAllTimePopular(uid, count, callback) {
		Topics.getTopicsFromSet('topics:posts', uid, 0, count - 1, function (err, data) {
			callback(err, data ? data.topics : null);
		});
	}
 
	function getTopics(tids, uid, count, callback) {
		async.waterfall([
			function (next) {
				Topics.getTopicsFields(tids, ['tid', 'postcount', 'deleted'], next);
			},
			function (topics, next) {
				tids = topics.filter(function (topic) {
					return topic && parseInt(topic.deleted, 10) !== 1;
				}).sort(function (a, b) {
					return b.postcount - a.postcount;
				}).slice(0, count).map(function (topic) {
					return topic.tid;
				});
				privileges.topics.filterTids('read', tids, uid, next);
			},
			function (tids, next) {
				Topics.getTopicsByTids(tids, uid, next);
			}
		], callback);
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/posts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/posts.js

Statements: 2.58% (5 / 194)      Branches: 0% (0 / 112)      Functions: 0% (0 / 76)      Lines: 2.59% (5 / 193)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389      2 2 2   2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             1                                                                            
 
'use strict';
 
var async = require('async');
var _ = require('underscore');
var validator = require('validator');
 
var db = require('../database');
var user = require('../user');
var posts = require('../posts');
var meta = require('../meta');
var plugins = require('../plugins');
 
module.exports = function (Topics) {
 
	Topics.onNewPostMade = function (postData, callback) {
		async.series([
			function (next) {
				Topics.increasePostCount(postData.tid, next);
			},
			function (next) {
				Topics.updateTimestamp(postData.tid, postData.timestamp, next);
			},
			function (next) {
				Topics.addPostToTopic(postData.tid, postData, next);
			}
		], callback);
	};
 
	Topics.getTopicPosts = function (tid, set, start, stop, uid, reverse, callback) {
		callback = callback || function () {};
		async.parallel({
			posts: function (next) {
				posts.getPostsFromSet(set, start, stop, uid, reverse, next);
			},
			postCount: function (next) {
				Topics.getTopicField(tid, 'postcount', next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			Topics.calculatePostIndices(results.posts, start, stop, results.postCount, reverse);
 
			Topics.addPostData(results.posts, uid, callback);
		});
	};
 
	Topics.addPostData = function (postData, uid, callback) {
		if (!Array.isArray(postData) || !postData.length) {
			return callback(null, []);
		}
		var pids = postData.map(function (post) {
			return post && post.pid;
		});
 
		if (!Array.isArray(pids) || !pids.length) {
			return callback(null, []);
		}
 
		async.parallel({
			bookmarks: function (next) {
				posts.hasBookmarked(pids, uid, next);
			},
			voteData: function (next) {
				posts.getVoteStatusByPostIDs(pids, uid, next);
			},
			userData: function (next) {
				var uids = [];
 
				for(var i = 0; i < postData.length; ++i) {
					if (postData[i] && uids.indexOf(postData[i].uid) === -1) {
						uids.push(postData[i].uid);
					}
				}
 
				posts.getUserInfoForPosts(uids, uid, function (err, users) {
					if (err) {
						return next(err);
					}
 
					var userData = {};
					users.forEach(function (user, index) {
						userData[uids[index]] = user;
					});
 
					next(null, userData);
				});
			},
			editors: function (next) {
				var editors = [];
				for(var i = 0; i < postData.length; ++i) {
					if (postData[i] && postData[i].editor && editors.indexOf(postData[i].editor) === -1) {
						editors.push(postData[i].editor);
					}
				}
 
				user.getUsersFields(editors, ['uid', 'username', 'userslug'], function (err, editors) {
					if (err) {
						return next(err);
					}
					var editorData = {};
					editors.forEach(function (editor) {
						editorData[editor.uid] = editor;
					});
					next(null, editorData);
				});
			},
			parents: function (next) {
				Topics.addParentPosts(postData, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			postData.forEach(function (postObj, i) {
				if (postObj) {
					postObj.deleted = parseInt(postObj.deleted, 10) === 1;
					postObj.user = parseInt(postObj.uid, 10) ? results.userData[postObj.uid] : _.clone(results.userData[postObj.uid]);
					postObj.editor = postObj.editor ? results.editors[postObj.editor] : null;
					postObj.bookmarked = results.bookmarks[i];
					postObj.upvoted = results.voteData.upvotes[i];
					postObj.downvoted = results.voteData.downvotes[i];
					postObj.votes = postObj.votes || 0;
					postObj.replies = postObj.replies || 0;
					postObj.selfPost = !!parseInt(uid, 10) && parseInt(uid, 10) === parseInt(postObj.uid, 10);
 
					// Username override for guests, if enabled
					if (parseInt(meta.config.allowGuestHandles, 10) === 1 && parseInt(postObj.uid, 10) === 0 && postObj.handle) {
						postObj.user.username = validator.escape(String(postObj.handle));
					}
				}
			});
 
			plugins.fireHook('filter:topics.addPostData', {
				posts: postData,
				uid: uid
			}, function (err, data) {
				callback(err, data ? data.posts : null);
			});
		});
	};
 
	Topics.modifyPostsByPrivilege = function (topicData, topicPrivileges) {
		var loggedIn = !!parseInt(topicPrivileges.uid, 10);
		topicData.posts.forEach(function (post) {
			if (post) {
				post.display_edit_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['posts:edit']);
				post.display_delete_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['posts:delete']);
				post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools;
				post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0;
				post.display_post_menu = topicPrivileges.isAdminOrMod || (post.selfPost && !topicData.locked) || ((loggedIn || topicData.postSharing.length) && !post.deleted);
				post.ip = topicPrivileges.isAdminOrMod ? post.ip : undefined;
 
				posts.modifyPostByPrivilege(post, topicPrivileges.isAdminOrMod);
			}
		});
	};
 
	Topics.addParentPosts = function (postData, callback) {
		var parentPids = postData.map(function (postObj) {
			return postObj && postObj.hasOwnProperty('toPid') ? parseInt(postObj.toPid, 10) : null;
		}).filter(Boolean);
 
		if (!parentPids.length) {
			return callback();
		}
 
		var parentPosts;
		async.waterfall([
			async.apply(posts.getPostsFields, parentPids, ['uid']),
			function (_parentPosts, next) {
				parentPosts = _parentPosts;
				var parentUids = parentPosts.map(function (postObj) { return parseInt(postObj.uid, 10); }).filter(function (uid, idx, users) {
					return users.indexOf(uid) === idx;
				});
 
				user.getUsersFields(parentUids, ['username'], next);
			},
			function (userData, next) {
				var usersMap = {};
				userData.forEach(function (user) {
					usersMap[user.uid] = user.username;
				});
				var parents = {};
				parentPosts.forEach(function (post, i) {
					parents[parentPids[i]] = {username: usersMap[post.uid]};
				});
 
				postData.forEach(function (post) {
					post.parent = parents[post.toPid];
				});
				next();
			}
		], callback);
	};
 
	Topics.calculatePostIndices = function (posts, start, stop, postCount, reverse) {
		posts.forEach(function (post, index) {
			if (reverse) {
				post.index = postCount - (start + index + 1);
			} else {
				post.index = start + index + 1;
			}
		});
	};
 
	Topics.getLatestUndeletedPid = function (tid, callback) {
		async.waterfall([
			function (next) {
				Topics.getLatestUndeletedReply(tid, next);
			},
			function (pid, next) {
				if (parseInt(pid, 10)) {
					return callback(null, pid.toString());
				}
				Topics.getTopicField(tid, 'mainPid', next);
			},
			function (mainPid, next) {
				posts.getPostFields(mainPid, ['pid', 'deleted'], next);
			},
			function (mainPost, next) {
				next(null, parseInt(mainPost.pid, 10) && parseInt(mainPost.deleted, 10) !== 1 ? mainPost.pid.toString() : null);
			}
		], callback);
	};
 
	Topics.getLatestUndeletedReply = function (tid, callback) {
		var isDeleted = false;
		var done = false;
		var latestPid = null;
		var index = 0;
		async.doWhilst(
			function (next) {
				db.getSortedSetRevRange('tid:' + tid + ':posts', index, index, function (err, pids) {
					if (err) {
						return next(err);
					}
 
					if (!Array.isArray(pids) || !pids.length) {
						done = true;
						return next();
					}
 
					posts.getPostField(pids[0], 'deleted', function (err, deleted) {
						if (err) {
							return next(err);
						}
 
						isDeleted = parseInt(deleted, 10) === 1;
						if (!isDeleted) {
							latestPid = pids[0];
						}
						++index;
						next();
					});
				});
			},
			function () {
				return isDeleted && !done;
			},
			function (err) {
				callback(err, latestPid);
			}
		);
	};
 
	Topics.addPostToTopic = function (tid, postData, callback) {
		async.waterfall([
			function (next) {
				Topics.getTopicField(tid, 'mainPid', next);
			},
			function (mainPid, next) {
				if (!parseInt(mainPid, 10)) {
					Topics.setTopicField(tid, 'mainPid', postData.pid, next);
				} else {
					async.parallel([
						function (next) {
							db.sortedSetAdd('tid:' + tid + ':posts', postData.timestamp, postData.pid, next);
						},
						function (next) {
							var upvotes = parseInt(postData.upvotes, 10) || 0;
							var downvotes = parseInt(postData.downvotes, 10) || 0;
							var votes = upvotes - downvotes;
							db.sortedSetAdd('tid:' + tid + ':posts:votes', votes, postData.pid, next);
						}
					], function (err) {
						next(err);
					});
				}
			},
			function (next) {
				db.sortedSetIncrBy('tid:' + tid + ':posters', 1, postData.uid, next);
			},
			function (count, next) {
				Topics.updateTeaser(tid, next);
			}
		], callback);
	};
 
	Topics.removePostFromTopic = function (tid, postData, callback) {
		async.waterfall([
			function (next) {
				db.sortedSetsRemove([
					'tid:' + tid + ':posts',
					'tid:' + tid + ':posts:votes'
				], postData.pid, next);
			},
			function (next) {
				db.sortedSetIncrBy('tid:' + tid + ':posters', -1, postData.uid, next);
			},
			function (count, next) {
				Topics.updateTeaser(tid, next);
			}
		], callback);
	};
 
	Topics.getPids = function (tid, callback) {
		async.parallel({
			mainPid: function (next) {
				Topics.getTopicField(tid, 'mainPid', next);
			},
			pids: function (next) {
				db.getSortedSetRange('tid:' + tid + ':posts', 0, -1, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			if (results.mainPid) {
				results.pids = [results.mainPid].concat(results.pids);
			}
			callback(null, results.pids);
		});
	};
 
	Topics.increasePostCount = function (tid, callback) {
		incrementFieldAndUpdateSortedSet(tid, 'postcount', 1, 'topics:posts', callback);
	};
 
	Topics.decreasePostCount = function (tid, callback) {
		incrementFieldAndUpdateSortedSet(tid, 'postcount', -1, 'topics:posts', callback);
	};
 
	Topics.increaseViewCount = function (tid, callback) {
		incrementFieldAndUpdateSortedSet(tid, 'viewcount', 1, 'topics:views', callback);
	};
 
	function incrementFieldAndUpdateSortedSet(tid, field, by, set, callback) {
		callback = callback || function () {};
		db.incrObjectFieldBy('topic:' + tid, field, by, function (err, value) {
			if (err) {
				return callback(err);
			}
			db.sortedSetAdd(set, value, tid, callback);
		});
	}
 
	Topics.getTitleByPid = function (pid, callback) {
		Topics.getTopicFieldByPid('title', pid, callback);
	};
 
	Topics.getTopicFieldByPid = function (field, pid, callback) {
		posts.getPostField(pid, 'tid', function (err, tid) {
			if (err) {
				return callback(err);
			}
			Topics.getTopicField(tid, field, callback);
		});
	};
 
	Topics.getTopicDataByPid = function (pid, callback) {
		posts.getPostField(pid, 'tid', function (err, tid) {
			if (err) {
				return callback(err);
			}
			Topics.getTopicData(tid, callback);
		});
	};
 
	Topics.getPostCount = function (tid, callback) {
		db.getObjectField('topic:' + tid, 'postcount', callback);
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/recent.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/recent.js

Statements: 4.29% (3 / 70)      Branches: 0% (0 / 27)      Functions: 0% (0 / 29)      Lines: 4.29% (3 / 70)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161        2 2                                                                                         1                                                                                                                                                                                                                            
 
 
'use strict';
 
var async = require('async');
var db = require('../database');
var plugins = require('../plugins');
var privileges = require('../privileges');
var user = require('../user');
var categories =  require('../categories');
 
module.exports = function (Topics) {
	var terms = {
		day: 86400000,
		week: 604800000,
		month: 2592000000,
		year: 31104000000
	};
 
	Topics.getRecentTopics = function (cid, uid, start, stop, filter, callback) {
		var recentTopics = {
			nextStart : 0,
			topics: []
		};
 
		async.waterfall([
			function (next) {
				if (cid) {
					categories.getTopicIds(cid, 'cid:' + cid + ':tids', true, 0, 199, next);
				} else {
					db.getSortedSetRevRange('topics:recent', 0, 199, next);
				}
			},
			function (tids, next) {
				filterTids(tids, uid, filter, next);
			},
			function (tids, next) {
				recentTopics.topicCount = tids.length;
				tids = tids.slice(start, stop + 1);
				Topics.getTopicsByTids(tids, uid, next);
			},
			function (topicData, next) {
				recentTopics.topics = topicData;
				recentTopics.nextStart = stop + 1;
				next(null, recentTopics);
			}
		], callback);
	};
 
 
	function filterTids(tids, uid, filter, callback) {
		async.waterfall([
			function (next) {
				if (filter === 'watched') {
					Topics.filterWatchedTids(tids, uid, next);
				} else if (filter === 'new') {
					Topics.filterNewTids(tids, uid, next);
				} else {
					Topics.filterNotIgnoredTids(tids, uid, next);
				}
			},
			function (tids, next) {
				privileges.topics.filterTids('read', tids, uid, next);
			},
			function (tids, next) {
				async.parallel({
					ignoredCids: function (next) {
						if (filter === 'watched') {
							return next(null, []);
						}
						user.getIgnoredCategories(uid, next);
					},
					topicData: function (next) {
						Topics.getTopicsFields(tids, ['tid', 'cid'], next);
					}
				}, next);
			},
			function (results, next) {
				tids = results.topicData.filter(function (topic) {
					if (topic) {
						return results.ignoredCids.indexOf(topic.cid.toString()) === -1;
					} else {
						return false;
					}
				}).map(function (topic) {
					return topic.tid;
				});
				next(null, tids);
			}
		], callback);
	}
 
 
	Topics.getLatestTopics = function (uid, start, stop, term, callback) {
		async.waterfall([
			function (next) {
				Topics.getLatestTidsFromSet('topics:recent', start, stop, term, next);
			},
			function (tids, next) {
				Topics.getTopics(tids, uid, next);
			},
			function (topics, next) {
				next(null, {topics: topics, nextStart: stop + 1});
			}
		], callback);
	};
 
	Topics.getLatestTidsFromSet = function (set, start, stop, term, callback) {
		var since = terms.day;
		if (terms[term]) {
			since = terms[term];
		}
 
		var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
 
		db.getSortedSetRevRangeByScore(set, start, count, '+inf', Date.now() - since, callback);
	};
 
	Topics.updateTimestamp = function (tid, timestamp, callback) {
		async.parallel([
			function (next) {
				async.waterfall([
					function (next) {
						Topics.getTopicField(tid, 'deleted', next);
					},
					function (deleted, next) {
						if (parseInt(deleted, 10) === 1) {
							return next();
						}
						Topics.updateRecent(tid, timestamp, next);
					}
				], next);
			},
			function (next) {
				Topics.setTopicField(tid, 'lastposttime', timestamp, next);
			}
		], function (err) {
			callback(err);
		});
	};
 
	Topics.updateRecent = function (tid, timestamp, callback) {
		callback = callback || function () {};
		if (plugins.hasListeners('filter:topics.updateRecent')) {
			plugins.fireHook('filter:topics.updateRecent', {tid: tid, timestamp: timestamp}, function (err, data) {
				if (err) {
					return callback(err);
				}
				if (data && data.tid && data.timestamp) {
					db.sortedSetAdd('topics:recent', data.timestamp, data.tid, callback);
				} else {
					callback();
				}
			});
		} else {
			db.sortedSetAdd('topics:recent', timestamp, tid, callback);
		}
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/suggested.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/suggested.js

Statements: 18.18% (6 / 33)      Branches: 0% (0 / 6)      Functions: 0% (0 / 18)      Lines: 18.18% (6 / 33)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81      2 2   2                                                                     1                               1                     1                        
 
'use strict';
 
var async = require('async');
var _ = require('underscore');
 
var categories = require('../categories');
var search = require('../search');
 
module.exports = function (Topics) {
 
	Topics.getSuggestedTopics = function (tid, uid, start, stop, callback) {
		async.parallel({
			tagTids: function (next) {
				getTidsWithSameTags(tid, next);
			},
			searchTids: function (next) {
				getSearchTids(tid, next);
			},
			categoryTids: function (next) {
				getCategoryTids(tid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
			var tids = results.tagTids.concat(results.searchTids).concat(results.categoryTids);
			tids = tids.filter(function (_tid, index, array) {
				return parseInt(_tid, 10) !== parseInt(tid, 10) && array.indexOf(_tid) === index;
			});
 
			if (stop === -1) {
				tids = tids.slice(start);
			} else {
				tids = tids.slice(start, stop + 1);
			}
 
			Topics.getTopics(tids, uid, callback);
		});
	};
 
	function getTidsWithSameTags(tid, callback) {
		async.waterfall([
			function (next) {
				Topics.getTopicTags(tid, next);
			},
			function (tags, next) {
				async.map(tags, function (tag, next) {
					Topics.getTagTids(tag, 0, -1, next);
				}, next);
			},
			function (data, next) {
				next(null, _.unique(_.flatten(data)));
			}
		], callback);
	}
 
	function getSearchTids(tid, callback) {
		async.waterfall([
			function (next) {
				Topics.getTopicField(tid, 'title', next);
			},
			function (title, next) {
				search.searchQuery('topic', title, [], [], next);
			}
		], callback);
	}
 
	function getCategoryTids(tid, callback) {
		async.waterfall([
			function (next) {
				Topics.getTopicField(tid, 'cid', next);
			},
			function (cid, next) {
				categories.getTopicIds(cid, 'cid:' + cid + ':tids', true, 0, 9, next);
			}
		], callback);
	}
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/tags.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/tags.js

Statements: 2.97% (6 / 202)      Branches: 0% (0 / 109)      Functions: 0% (0 / 84)      Lines: 2.99% (6 / 201)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410      2   2                                                                                                                                               1                                                                                 1                                                                                                                                                                                                                                                                                                     1                                                                                         1                                                                                                                                                                                                      
 
'use strict';
 
var async = require('async');
 
var db = require('../database');
var meta = require('../meta');
var _ = require('underscore');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
 
 
module.exports = function (Topics) {
 
	Topics.createTags = function (tags, tid, timestamp, callback) {
		callback = callback || function () {};
 
		if (!Array.isArray(tags) || !tags.length) {
			return callback();
		}
 
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:tags.filter', {tags: tags, tid: tid}, next);
			},
			function (data, next) {
				tags = data.tags.slice(0, meta.config.maximumTagsPerTopic || 5);
				tags = tags.map(function (tag) {
					return utils.cleanUpTag(tag, meta.config.maximumTagLength);
				}).filter(function (tag, index, array) {
					return tag && tag.length >= (meta.config.minimumTagLength || 3) && array.indexOf(tag) === index;
				});
 
				var keys = tags.map(function (tag) {
					return 'tag:' + tag + ':topics';
				});
 
				async.parallel([
					async.apply(db.setAdd, 'topic:' + tid + ':tags', tags),
					async.apply(db.sortedSetsAdd, keys, timestamp, tid)
				], function (err) {
					if (err) {
						return next(err);
					}
					async.each(tags, updateTagCount, next);
				});
			}
		], callback);
	};
 
	Topics.createEmptyTag = function (tag, callback) {
		if (!tag) {
			return callback(new Error('[[error:invalid-tag]]'));
		}
 
		tag = utils.cleanUpTag(tag, meta.config.maximumTagLength);
		if (tag.length < (meta.config.minimumTagLength || 3)) {
			return callback(new Error('[[error:tag-too-short]]'));
		}
 
		async.waterfall([
			function (next) {
				db.isSortedSetMember('tags:topic:count', tag, next);
			},
			function (isMember, next) {
				if (isMember) {
					return next();
				}
				db.sortedSetAdd('tags:topic:count', 0, tag, next);
			}
		], callback);
	};
 
	Topics.updateTag = function (tag, data, callback) {
		db.setObject('tag:' + tag, data, callback);
	};
 
	function updateTagCount(tag, callback) {
		callback = callback || function () {};
		Topics.getTagTopicCount(tag, function (err, count) {
			if (err) {
				return callback(err);
			}
			count = count || 0;
 
			db.sortedSetAdd('tags:topic:count', count, tag, callback);
		});
	}
 
	Topics.getTagTids = function (tag, start, stop, callback) {
		db.getSortedSetRevRange('tag:' + tag + ':topics', start, stop, callback);
	};
 
	Topics.getTagTopicCount = function (tag, callback) {
		db.sortedSetCard('tag:' + tag + ':topics', callback);
	};
 
	Topics.deleteTags = function (tags, callback) {
		if (!Array.isArray(tags) || !tags.length) {
			return callback();
		}
 
		async.series([
			function (next) {
				removeTagsFromTopics(tags, next);
			},
			function (next) {
				var keys = tags.map(function (tag) {
					return 'tag:' + tag + ':topics';
				});
				db.deleteAll(keys, next);
			},
			function (next) {
				db.sortedSetRemove('tags:topic:count', tags, next);
			}
		], callback);
	};
 
	function removeTagsFromTopics(tags, callback) {
		async.eachLimit(tags, 50, function (tag, next) {
			db.getSortedSetRange('tag:' + tag + ':topics', 0, -1, function (err, tids) {
				if (err || !tids.length) {
					return next(err);
				}
				var keys = tids.map(function (tid) {
					return 'topic:' + tid + ':tags';
				});
 
				db.setsRemove(keys, tag, next);
			});
		}, callback);
	}
 
	Topics.deleteTag = function (tag) {
		db.delete('tag:' + tag + ':topics');
		db.sortedSetRemove('tags:topic:count', tag);
	};
 
	Topics.getTags = function (start, stop, callback) {
		db.getSortedSetRevRangeWithScores('tags:topic:count', start, stop, function (err, tags) {
			if (err) {
				return callback(err);
			}
 
			Topics.getTagData(tags, callback);
		});
	};
 
	Topics.getTagData = function (tags, callback) {
		var keys = tags.map(function (tag) {
			return 'tag:' + tag.value;
		});
 
		db.getObjects(keys, function (err, tagData) {
			if (err) {
				return callback(err);
			}
 
			tags.forEach(function (tag, index) {
				tag.color = tagData[index] ? tagData[index].color : '';
				tag.bgColor = tagData[index] ? tagData[index].bgColor : '';
			});
			callback(null, tags);
		});
	};
 
	Topics.getTopicTags = function (tid, callback) {
		db.getSetMembers('topic:' + tid + ':tags', callback);
	};
 
	Topics.getTopicTagsObjects = function (tid, callback) {
		Topics.getTopicsTagsObjects([tid], function (err, data) {
			callback(err, Array.isArray(data) && data.length ? data[0] : []);
		});
	};
 
	Topics.getTopicsTagsObjects = function (tids, callback) {
		var sets = tids.map(function (tid) {
			return 'topic:' + tid + ':tags';
		});
 
		db.getSetsMembers(sets, function (err, topicTags) {
			if (err) {
				return callback(err);
			}
 
			var uniqueTopicTags = _.uniq(_.flatten(topicTags));
 
			var tags = uniqueTopicTags.map(function (tag) {
				return {value: tag};
			});
 
			async.parallel({
				tagData: function (next) {
					Topics.getTagData(tags, next);
				},
				counts: function (next) {
					db.sortedSetScores('tags:topic:count', uniqueTopicTags, next);
				}
			}, function (err, results) {
				if (err) {
					return callback(err);
				}
 
				results.tagData.forEach(function (tag, index) {
					tag.score = results.counts[index] ? results.counts[index] : 0;
				});
 
				var tagData = _.object(uniqueTopicTags, results.tagData);
 
				topicTags.forEach(function (tags, index) {
					if (Array.isArray(tags)) {
						topicTags[index] = tags.map(function (tag) {return tagData[tag];});
					}
				});
 
				callback(null, topicTags);
			});
		});
	};
 
	Topics.updateTags = function (tid, tags, callback) {
		callback = callback || function () {};
		async.waterfall([
			function (next) {
				Topics.deleteTopicTags(tid, next);
			},
			function (next) {
				Topics.getTopicField(tid, 'timestamp', next);
			},
			function (timestamp, next) {
				Topics.createTags(tags, tid, timestamp, next);
			}
		], callback);
	};
 
	Topics.deleteTopicTags = function (tid, callback) {
		Topics.getTopicTags(tid, function (err, tags) {
			if (err) {
				return callback(err);
			}
 
			async.series([
				function (next) {
					db.delete('topic:' + tid + ':tags', next);
				},
				function (next) {
					var sets = tags.map(function (tag) {
						return 'tag:' + tag + ':topics';
					});
 
					db.sortedSetsRemove(sets, tid, next);
				},
				function (next) {
					async.each(tags, function (tag, next) {
						updateTagCount(tag, next);
					}, next);
				}
			], function (err) {
				callback(err);
			});
		});
	};
 
	Topics.searchTags = function (data, callback) {
		function done(matches) {
			plugins.fireHook('filter:tags.search', {data: data, matches: matches}, function (err, data) {
				callback(err, data ? data.matches : []);
			});
		}
 
 
		if (!data || !data.query) {
			return callback(null, []);
		}
 
		if (plugins.hasListeners('filter:topics.searchTags')) {
			return plugins.fireHook('filter:topics.searchTags', {data: data}, function (err, data) {
				if (err) {
					return callback(err);
				}
				done(data.matches);
			});
		}
 
		findMatches(data.query, function (err, matches) {
			if (err) {
				return callback(err);
			}
			done(matches);
		});
	};
 
	Topics.autocompleteTags = function (data, callback) {
		if (!data || !data.query) {
			return callback(null, []);
		}
 
		if (plugins.hasListeners('filter:topics.autocompleteTags')) {
			return plugins.fireHook('filter:topics.autocompleteTags', {data: data}, function (err, data) {
				if (err) {
					return callback(err);
				}
				callback(null, data.matches);
			});
		}
 
		findMatches(data.query, callback);
	};
 
	function findMatches(query, callback) {
		db.getSortedSetRevRange('tags:topic:count', 0, -1, function (err, tags) {
			if (err) {
				return callback(err);
			}
 
			query = query.toLowerCase();
 
			var matches = [];
			for(var i = 0; i < tags.length; ++i) {
				if (tags[i].toLowerCase().startsWith(query)) {
					matches.push(tags[i]);
					if (matches.length > 19) {
						break;
					}
				}
			}
 
			matches = matches.sort(function (a, b) {
				return a > b;
			});
			callback(null, matches);
		});
	}
 
	Topics.searchAndLoadTags = function (data, callback) {
		var searchResult = {
			tags: [],
			matchCount: 0,
			pageCount: 1
		};
 
		if (!data.query || !data.query.length) {
			return callback(null, searchResult);
		}
		Topics.searchTags(data, function (err, tags) {
			if (err) {
				return callback(err);
			}
			async.parallel({
				counts: function (next) {
					db.sortedSetScores('tags:topic:count', tags, next);
				},
				tagData: function (next) {
					tags = tags.map(function (tag) {
						return {value: tag};
					});
 
					Topics.getTagData(tags, next);
				}
			}, function (err, results) {
				if (err) {
					return callback(err);
				}
				results.tagData.forEach(function (tag, index) {
					tag.score = results.counts[index];
				});
				results.tagData.sort(function (a, b) {
					return b.score - a.score;
				});
				searchResult.tags = results.tagData;
				searchResult.matchCount = results.tagData.length;
				searchResult.pageCount = 1;
				callback(null, searchResult);
			});
		});
	};
 
	Topics.getRelatedTopics = function (topicData, uid, callback) {
		if (plugins.hasListeners('filter:topic.getRelatedTopics')) {
			return plugins.fireHook('filter:topic.getRelatedTopics', {topic: topicData, uid: uid}, callback);
		}
 
		var maximumTopics = parseInt(meta.config.maximumRelatedTopics, 10) || 0;
		if (maximumTopics === 0 || !topicData.tags || !topicData.tags.length) {
			return callback(null, []);
		}
 
		maximumTopics = maximumTopics || 5;
 
		async.waterfall([
			function (next) {
				async.map(topicData.tags, function (tag, next) {
					Topics.getTagTids(tag.value, 0, 5, next);
				}, next);
			},
			function (tids, next) {
				tids = _.shuffle(_.unique(_.flatten(tids))).slice(0, maximumTopics);
				Topics.getTopics(tids, uid, next);
			},
			function (topics, next) {
				topics = topics.filter(function (topic) {
					return topic && !topic.deleted && parseInt(topic.uid, 10) !== parseInt(uid, 10);
				});
				next(null, topics);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/teaser.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/teaser.js

Statements: 4.17% (3 / 72)      Branches: 0% (0 / 41)      Functions: 0% (0 / 20)      Lines: 4.17% (3 / 72)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141        2 2   2                                                                                                                                                                                                                                                                          
 
 
'use strict';
 
var async = require('async');
var S = require('string');
 
var meta = require('../meta');
var user = require('../user');
var posts = require('../posts');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
 
module.exports = function (Topics) {
 
	Topics.getTeasers = function (topics, callback) {
		if (!Array.isArray(topics) || !topics.length) {
			return callback(null, []);
		}
 
		var counts = [];
		var teaserPids = [];
		var postData;
		var tidToPost = {};
 
		topics.forEach(function (topic) {
			counts.push(topic && (parseInt(topic.postcount, 10) || 0));
			if (topic) {
				if (topic.teaserPid === 'null') {
					delete topic.teaserPid;
				}
 
				switch(meta.config.teaserPost) {
					case 'first':
						teaserPids.push(topic.mainPid);
						break;
 
					case 'last-post':
						teaserPids.push(topic.teaserPid || topic.mainPid);
						break;
 
					case 'last-reply':	// intentional fall-through
					default:
						teaserPids.push(topic.teaserPid);
						break;
				}
			}
		});
 
		async.waterfall([
			function (next) {
				posts.getPostsFields(teaserPids, ['pid', 'uid', 'timestamp', 'tid', 'content'], next);
			},
			function (_postData, next) {
				postData = _postData;
				var uids = postData.map(function (post) {
					return post.uid;
				}).filter(function (uid, index, array) {
					return array.indexOf(uid) === index;
				});
 
				user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
			},
			function (usersData, next) {
				var users = {};
				usersData.forEach(function (user) {
					users[user.uid] = user;
				});
 
 
				async.each(postData, function (post, next) {
					// If the post author isn't represented in the retrieved users' data, then it means they were deleted, assume guest.
					if (!users.hasOwnProperty(post.uid)) {
						post.uid = 0;
					}
 
					post.user = users[post.uid];
					post.timestampISO = utils.toISOString(post.timestamp);
					tidToPost[post.tid] = post;
					posts.parsePost(post, next);
				}, next);
			},
			function (next) {
				var teasers = topics.map(function (topic, index) {
					if (!topic) {
						return null;
					}
					if (tidToPost[topic.tid]) {
						tidToPost[topic.tid].index = meta.config.teaserPost === 'first' ? 1 : counts[index];
						if (tidToPost[topic.tid].content) {
							var s = S(tidToPost[topic.tid].content);
							tidToPost[topic.tid].content = s.stripTags.apply(s, utils.stripTags).s;
						}
					}
					return tidToPost[topic.tid];
				});
 
				plugins.fireHook('filter:teasers.get', {teasers: teasers}, next);
			},
			function (data, next) {
				next(null, data.teasers);
			}
		], callback);
	};
 
	Topics.getTeasersByTids = function (tids, callback) {
		if (!Array.isArray(tids) || !tids.length) {
			return callback(null, []);
		}
		async.waterfall([
			function (next) {
				Topics.getTopicsFields(tids, ['tid', 'postcount', 'teaserPid'], next);
			},
			function (topics, next) {
				Topics.getTeasers(topics, next);
			}
		], callback);
	};
 
	Topics.getTeaser = function (tid, callback) {
		Topics.getTeasersByTids([tid], function (err, teasers) {
			callback(err, Array.isArray(teasers) && teasers.length ? teasers[0] : null);
		});
	};
 
	Topics.updateTeaser = function (tid, callback) {
		Topics.getLatestUndeletedReply(tid, function (err, pid) {
			if (err) {
				return callback(err);
			}
 
			pid = pid || null;
			if (pid) {
				Topics.setTopicField(tid, 'teaserPid', pid, callback);
			} else {
				Topics.deleteTopicField(tid, 'teaserPid', callback);
			}
		});
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/thumb.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/thumb.js

Statements: 21.28% (10 / 47)      Branches: 0% (0 / 18)      Functions: 0% (0 / 11)      Lines: 21.28% (10 / 47)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93      2 2 2 2 2 2 2 2   2                                                                                                                                       1                        
 
'use strict';
 
var async = require('async');
var nconf = require('nconf');
var winston = require('winston');
var path = require('path');
var fs = require('fs');
var request = require('request');
var mime = require('mime');
var validator = require('validator');
 
var meta = require('../meta');
var image = require('../image');
var file = require('../file');
var plugins = require('../plugins');
 
module.exports = function (Topics) {
 
	Topics.resizeAndUploadThumb = function (data, callback) {
		if (!data.thumb || !validator.isURL(data.thumb)) {
			return callback();
		}
 
		var pathToUpload;
		var filename;
 
		async.waterfall([
			function (next) {
				request.head(data.thumb, next);
			},
			function (res, body, next) {
 
				var type = res.headers['content-type'];
				if (!type.match(/image./)) {
					return next(new Error('[[error:invalid-file]]'));
				}
 
				var extension = path.extname(data.thumb);
				if (!extension) {
					extension = '.' + mime.extension(type);
				}
				filename = Date.now() + '-topic-thumb' + extension;
				pathToUpload = path.join(nconf.get('base_dir'), nconf.get('upload_path'), 'files', filename);
 
				request(data.thumb).pipe(fs.createWriteStream(pathToUpload)).on('close', next);
			},
			function (next) {
				file.isFileTypeAllowed(pathToUpload, next);
			},
			function (next) {
				var size = parseInt(meta.config.topicThumbSize, 10) || 120;
				image.resizeImage({
					path: pathToUpload,
					extension: path.extname(pathToUpload),
					width: size,
					height: size
				}, next);
			},
			function (next) {
				if (!plugins.hasListeners('filter:uploadImage')) {
					data.thumb = path.join(nconf.get('upload_url'), 'files', filename);
					return callback();
				}
 
				plugins.fireHook('filter:uploadImage', {image: {path: pathToUpload, name: ''}, uid: data.uid}, next);
			},
			function (uploadedFile, next) {
				deleteFile(pathToUpload);
				data.thumb = uploadedFile.url;
				next();
			}
		], function (err) {
			if (err) {
				deleteFile(pathToUpload);
			}
			callback(err);
		});
	};
 
	function deleteFile(path) {
		if (path) {
			fs.unlink(path, function (err) {
				if (err) {
					winston.error(err);
				}
			});
		}
	}
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/tools.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/tools.js

Statements: 4.35% (5 / 115)      Branches: 0% (0 / 48)      Functions: 0% (0 / 44)      Lines: 4.35% (5 / 115)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287    2   2                                       1                                                                                                                                                                               1                                                                                           1                                                                                                                                                                                                                                                                
'use strict';
 
var async = require('async');
 
var db = require('../database');
var categories = require('../categories');
var plugins = require('../plugins');
var privileges = require('../privileges');
 
 
module.exports = function (Topics) {
 
	var topicTools = {};
	Topics.tools = topicTools;
 
 
	topicTools.delete = function (tid, uid, callback) {
		toggleDelete(tid, uid, true, callback);
	};
 
	topicTools.restore = function (tid, uid, callback) {
		toggleDelete(tid, uid, false, callback);
	};
 
	function toggleDelete(tid, uid, isDelete, callback) {
		var topicData;
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-topic]]'));
				}
				privileges.topics.canDelete(tid, uid, next);
			},
			function (canDelete, next) {
				if (!canDelete) {
					return next(new Error('[[error:no-privileges]]'));
				}
				Topics.getTopicFields(tid, ['tid', 'cid', 'uid', 'deleted', 'title', 'mainPid'], next);
			},
			function (_topicData, next) {
				topicData = _topicData;
 
				if (parseInt(topicData.deleted, 10) === 1 && isDelete) {
					return callback(new Error('[[error:topic-already-deleted]]'));
				} else if(parseInt(topicData.deleted, 10) !== 1 && !isDelete) {
					return callback(new Error('[[error:topic-already-restored]]'));
				}
 
				Topics[isDelete ? 'delete' : 'restore'](tid, uid, next);
			},
			function (next) {
				topicData.deleted = isDelete ? 1 : 0;
 
				if (isDelete) {
					plugins.fireHook('action:topic.delete', topicData);
				} else {
					plugins.fireHook('action:topic.restore', topicData);
				}
 
				var data = {
					tid: tid,
					cid: topicData.cid,
					isDelete: isDelete,
					uid: uid
				};
 
				next(null, data);
			}
		], callback);
	}
 
	topicTools.purge = function (tid, uid, callback) {
		var cid;
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return callback();
				}
				privileges.topics.canPurge(tid, uid, next);
			},
			function (canPurge, next) {
				if (!canPurge) {
					return next(new Error('[[error:no-privileges]]'));
				}
 
				Topics.getTopicField(tid, 'cid', next);
			},
			function (_cid, next) {
				cid = _cid;
 
				Topics.purgePostsAndTopic(tid, uid, next);
			},
			function (next) {
				next(null, {tid: tid, cid: cid, uid: uid});
			}
		], callback);
	};
 
	topicTools.lock = function (tid, uid, callback) {
		toggleLock(tid, uid, true, callback);
	};
 
	topicTools.unlock = function (tid, uid, callback) {
		toggleLock(tid, uid, false, callback);
	};
 
	function toggleLock(tid, uid, lock, callback) {
		callback = callback || function () {};
 
		var cid;
 
		async.waterfall([
			function (next) {
				Topics.getTopicField(tid, 'cid', next);
			},
			function (_cid, next) {
				cid = _cid;
				if (!cid) {
					return next(new Error('[[error:no-topic]]'));
				}
				privileges.categories.isAdminOrMod(cid, uid, next);
			},
			function (isAdminOrMod, next) {
				if (!isAdminOrMod) {
					return next(new Error('[[error:no-privileges]]'));
				}
 
				Topics.setTopicField(tid, 'locked', lock ? 1 : 0, next);
			},
			function (next) {
				var data = {
					tid: tid,
					isLocked: lock,
					uid: uid,
					cid: cid
				};
 
				plugins.fireHook('action:topic.lock', data);
 
				next(null, data);
			}
		], callback);
	}
 
	topicTools.pin = function (tid, uid, callback) {
		togglePin(tid, uid, true, callback);
	};
 
	topicTools.unpin = function (tid, uid, callback) {
		togglePin(tid, uid, false, callback);
	};
 
	function togglePin(tid, uid, pin, callback) {
		var topicData;
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return callback(new Error('[[error:no-topic]]'));
				}
				Topics.getTopicFields(tid, ['cid', 'lastposttime', 'postcount'], next);
			},
			function (_topicData, next) {
				topicData = _topicData;
				privileges.categories.isAdminOrMod(_topicData.cid, uid, next);
			},
			function (isAdminOrMod, next) {
				if (!isAdminOrMod) {
					return next(new Error('[[error:no-privileges]]'));
				}
 
				async.parallel([
					async.apply(Topics.setTopicField, tid, 'pinned', pin ? 1 : 0),
					function (next) {
						if (pin) {
							async.parallel([
								async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:pinned', Date.now(), tid),
								async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids', tid),
								async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:posts', tid),
							], next);
						} else {
							async.parallel([
								async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:pinned', tid),
								async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids', topicData.lastposttime, tid),
								async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:posts', topicData.postcount, tid),
							], next);
						}
					}
				], next);
			},
			function (results, next) {
				var data = {
					tid: tid,
					isPinned: pin,
					uid: uid,
					cid: topicData.cid
				};
 
				plugins.fireHook('action:topic.pin', data);
 
				next(null, data);
			}
		], callback);
	}
 
	topicTools.move = function (tid, cid, uid, callback) {
		var topic;
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-topic]]'));
				}
				Topics.getTopicFields(tid, ['cid', 'lastposttime', 'pinned', 'deleted', 'postcount'], next);
			},
			function (topicData, next) {
				topic = topicData;
				db.sortedSetsRemove([
					'cid:' + topicData.cid + ':tids',
					'cid:' + topicData.cid + ':tids:pinned',
					'cid:' + topicData.cid + ':tids:posts'
				], tid, next);
			},
			function (next) {
				if (parseInt(topic.pinned, 10)) {
					db.sortedSetAdd('cid:' + cid + ':tids:pinned', Date.now(), tid, next);
				} else {
					async.parallel([
						function (next) {
							db.sortedSetAdd('cid:' + cid + ':tids', topic.lastposttime, tid, next);
						},
						function (next) {
							topic.postcount = topic.postcount || 0;
							db.sortedSetAdd('cid:' + cid + ':tids:posts', topic.postcount, tid, next);
						}
					], next);
				}
			}
		], function (err) {
			if (err) {
				return callback(err);
			}
			var oldCid = topic.cid;
			categories.moveRecentReplies(tid, oldCid, cid);
 
			async.parallel([
				function (next) {
					categories.incrementCategoryFieldBy(oldCid, 'topic_count', -1, next);
				},
				function (next) {
					categories.incrementCategoryFieldBy(cid, 'topic_count', 1, next);
				},
				function (next) {
					Topics.setTopicFields(tid, {
						cid: cid,
						oldCid: oldCid
					}, next);
				}
			], function (err) {
				if (err) {
					return callback(err);
				}
				plugins.fireHook('action:topic.move', {
					tid: tid,
					fromCid: oldCid,
					toCid: cid,
					uid: uid
				});
				callback();
			});
		});
	};
 
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/unread.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/unread.js

Statements: 1.78% (3 / 169)      Branches: 0% (0 / 98)      Functions: 0% (0 / 73)      Lines: 1.78% (3 / 169)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380      2   2                                                                                                                                                                                                                                                                                                   1                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
 
'use strict';
 
var async = require('async');
 
var db = require('../database');
var user = require('../user');
var notifications = require('../notifications');
var categories = require('../categories');
var privileges = require('../privileges');
var meta = require('../meta');
var utils = require('../../public/src/utils');
 
module.exports = function (Topics) {
 
	Topics.getTotalUnread = function (uid, filter, callback) {
		if (!callback) {
			callback = filter;
			filter = '';
		}
		Topics.getUnreadTids(0, uid, filter, function (err, tids) {
			callback(err, Array.isArray(tids) ? tids.length : 0);
		});
	};
 
 
	Topics.getUnreadTopics = function (cid, uid, start, stop, filter, callback) {
 
		var unreadTopics = {
			showSelect: true,
			nextStart : 0,
			topics: []
		};
 
		async.waterfall([
			function (next) {
				Topics.getUnreadTids(cid, uid, filter, next);
			},
			function (tids, next) {
				unreadTopics.topicCount = tids.length;
 
				if (!tids.length) {
					return next(null, []);
				}
 
				if (stop === -1) {
					tids = tids.slice(start);
				} else {
					tids = tids.slice(start, stop + 1);
				}
 
				Topics.getTopicsByTids(tids, uid, next);
			},
			function (topicData, next) {
				if (!Array.isArray(topicData) || !topicData.length) {
					return next(null, unreadTopics);
				}
 
				unreadTopics.topics = topicData;
				unreadTopics.nextStart = stop + 1;
				next(null, unreadTopics);
			}
		], callback);
	};
 
	Topics.unreadCutoff = function () {
		return Date.now() - (parseInt(meta.config.unreadCutoff, 10) || 2) * 86400000;
	};
 
	Topics.getUnreadTids = function (cid, uid, filter, callback) {
		uid = parseInt(uid, 10);
		if (uid === 0) {
			return callback(null, []);
		}
 
		var cutoff = Topics.unreadCutoff();
 
		var ignoredCids;
 
		async.waterfall([
			function (next) {
				async.parallel({
					ignoredCids: function (next) {
						if (filter === 'watched') {
							return next(null, []);
						}
						user.getIgnoredCategories(uid, next);
					},
					ignoredTids: function (next) {
						user.getIgnoredTids(uid, 0, -1, next);
					},
					recentTids: function (next) {
						db.getSortedSetRevRangeByScoreWithScores('topics:recent', 0, -1, '+inf', cutoff, next);
					},
					userScores: function (next) {
						db.getSortedSetRevRangeByScoreWithScores('uid:' + uid + ':tids_read', 0, -1, '+inf', cutoff, next);
					},
					tids_unread: function (next) {
						db.getSortedSetRevRangeWithScores('uid:' + uid + ':tids_unread', 0, -1, next);
					}
				}, next);
			},
			function (results, next) {
				if (results.recentTids && !results.recentTids.length && !results.tids_unread.length) {
					return callback(null, []);
				}
 
				ignoredCids = results.ignoredCids;
 
				var userRead = {};
				results.userScores.forEach(function (userItem) {
					userRead[userItem.value] = userItem.score;
				});
 
				results.recentTids = results.recentTids.concat(results.tids_unread);
				results.recentTids.sort(function (a, b) {
					return b.score - a.score;
				});
 
				var tids = results.recentTids.filter(function (recentTopic) {
					if (results.ignoredTids.indexOf(recentTopic.value.toString()) !== -1) {
						return false;
					}
					switch (filter) {
						case 'new':
							return !userRead[recentTopic.value];
						default:
							return !userRead[recentTopic.value] || recentTopic.score > userRead[recentTopic.value];
					}
				}).map(function (topic) {
					return topic.value;
				}).filter(function (tid, index, array) {
					return array.indexOf(tid) === index;
				});
 
				if (filter === 'watched') {
					Topics.filterWatchedTids(tids, uid, next);
				} else {
					next(null, tids);
				}
			},
			function (tids, next) {
 
				tids = tids.slice(0, 200);
 
				filterTopics(uid, tids, cid, ignoredCids, filter, next);
			}
		], callback);
	};
 
 
	function filterTopics(uid, tids, cid, ignoredCids, filter, callback) {
		if (!Array.isArray(ignoredCids) || !tids.length) {
			return callback(null, tids);
		}
 
		async.waterfall([
			function (next) {
				privileges.topics.filterTids('read', tids, uid, next);
			},
			function (tids, next) {
				async.parallel({
					topics: function (next) {
						Topics.getTopicsFields(tids, ['tid', 'cid'], next);
					},
					isTopicsFollowed: function (next) {
						if (filter === 'watched' || filter === 'new') {
							return next(null, []);
						}
						db.sortedSetScores('uid:' + uid + ':followed_tids', tids, next);
					}
				}, next);
			},
			function (results, next) {
				var topics = results.topics;
				tids = topics.filter(function (topic, index) {
					return topic && topic.cid &&
						(!!results.isTopicsFollowed[index] || ignoredCids.indexOf(topic.cid.toString()) === -1) &&
						(!cid || parseInt(cid, 10) === parseInt(topic.cid, 10));
				}).map(function (topic) {
					return topic.tid;
				});
				next(null, tids);
			}
		], callback);
	}
 
	Topics.pushUnreadCount = function (uid, callback) {
		callback = callback || function () {};
 
		if (!uid || parseInt(uid, 10) === 0) {
			return callback();
		}
		Topics.getTotalUnread(uid, function (err, count) {
			if (err) {
				return callback(err);
			}
 
			require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', count);
			callback();
		});
	};
 
	Topics.markAsUnreadForAll = function (tid, callback) {
		Topics.markCategoryUnreadForAll(tid, callback);
	};
 
	Topics.markAsRead = function (tids, uid, callback) {
		callback = callback || function () {};
		if (!Array.isArray(tids) || !tids.length) {
			return callback();
		}
 
		tids = tids.filter(function (tid, index, array) {
			return tid && utils.isNumber(tid) && array.indexOf(tid) === index;
		});
 
		if (!tids.length) {
			return callback(null, false);
		}
 
		async.waterfall([
			function (next) {
				async.parallel({
					topicScores: async.apply(db.sortedSetScores, 'topics:recent', tids),
					userScores: async.apply(db.sortedSetScores, 'uid:' + uid + ':tids_read', tids)
				}, next);
			},
			function (results, next) {
				tids = tids.filter(function (tid, index) {
					return results.topicScores[index] && (!results.userScores[index] || results.userScores[index] < results.topicScores[index]);
				});
 
				if (!tids.length) {
					return callback(null, false);
				}
 
				var now = Date.now();
				var scores = tids.map(function () {
					return now;
				});
 
				async.parallel({
					markRead: async.apply(db.sortedSetAdd, 'uid:' + uid + ':tids_read', scores, tids),
					markUnread: async.apply(db.sortedSetRemove, 'uid:' + uid + ':tids_unread', tids),
					topicData: async.apply(Topics.getTopicsFields, tids, ['cid'])
				}, next);
			},
			function (results, next) {
				var cids = results.topicData.map(function (topic) {
					return topic && topic.cid;
				}).filter(function (topic, index, array) {
					return topic && array.indexOf(topic) === index;
				});
 
				categories.markAsRead(cids, uid, next);
			},
			function (next) {
				next(null, true);
			}
		], callback);
	};
 
	Topics.markAllRead = function (uid, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRevRangeByScore('topics:recent', 0, -1, '+inf', Topics.unreadCutoff(), next);
			},
			function (tids, next) {
				Topics.markTopicNotificationsRead(tids, uid);
				Topics.markAsRead(tids, uid, next);
			},
			function (markedRead, next) {
				db.delete('uid:' + uid + ':tids_unread', next);
			}
		], callback);
	};
 
	Topics.markTopicNotificationsRead = function (tids, uid, callback) {
		callback = callback || function () {};
		if (!Array.isArray(tids) || !tids.length) {
			return callback();
		}
 
		async.waterfall([
			function (next) {
				user.notifications.getUnreadByField(uid, 'tid', tids, next);
			},
			function (nids, next) {
				notifications.markReadMultiple(nids, uid, next);
			},
			function (next) {
				user.notifications.pushCount(uid);
				next();
			}
		], callback);
	};
 
	Topics.markCategoryUnreadForAll = function (tid, callback) {
		async.waterfall([
			function (next) {
				Topics.getTopicField(tid, 'cid', next);
			},
			function (cid, next) {
				categories.markAsUnreadForAll(cid, next);
			}
		], callback);
	};
 
	Topics.hasReadTopics = function (tids, uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, tids.map(function () {
				return false;
			}));
		}
 
		async.parallel({
			recentScores: function (next) {
				db.sortedSetScores('topics:recent', tids, next);
			},
			userScores: function (next) {
				db.sortedSetScores('uid:' + uid + ':tids_read', tids, next);
			},
			tids_unread: function (next) {
				db.sortedSetScores('uid:' + uid + ':tids_unread', tids, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var cutoff = Topics.unreadCutoff();
			var result = tids.map(function (tid, index) {
				return !results.tids_unread[index] &&
					(results.recentScores[index] < cutoff ||
					!!(results.userScores[index] && results.userScores[index] >= results.recentScores[index]));
			});
 
			callback(null, result);
		});
	};
 
	Topics.hasReadTopic = function (tid, uid, callback) {
		Topics.hasReadTopics([tid], uid, function (err, hasRead) {
			callback(err, Array.isArray(hasRead) && hasRead.length ? hasRead[0] : false);
		});
	};
 
	Topics.markUnread = function (tid, uid, callback) {
		async.waterfall([
			function (next) {
				Topics.exists(tid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-topic]]'));
				}
				db.sortedSetRemove('uid:' + uid + ':tids_read', tid, next);
			},
			function (next) {
				db.sortedSetAdd('uid:' + uid + ':tids_unread', Date.now(), tid, next);
			}
		], callback);
	};
 
	Topics.filterNewTids = function (tids, uid, callback) {
		db.sortedSetScores('uid:' + uid + ':tids_read', tids, function (err, scores) {
			if (err) {
				return callback(err);
			}
			tids = tids.filter(function (tid, index) {
				return tid && !scores[index];
			});
			callback(null, tids);
		});
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/user.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/topics/user.js

Statements: 16.67% (2 / 12)      Branches: 0% (0 / 2)      Functions: 0% (0 / 4)      Lines: 16.67% (2 / 12)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25        2 2                                      
 
 
'use strict';
 
var async = require('async');
var db = require('../database');
var posts = require('../posts');
 
module.exports = function (Topics) {
 
	Topics.isOwner = function (tid, uid, callback) {
		uid = parseInt(uid, 10);
		if (!uid) {
			return callback(null, false);
		}
		Topics.getTopicField(tid, 'uid', function (err, author) {
			callback(err, parseInt(author, 10) === uid);
		});
	};
 
	Topics.getUids = function (tid, callback) {
		db.getSortedSetRevRangeByScore('tid:' + tid + ':posters', 0, -1, '+inf', 1, callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/

Statements: 6.46% (99 / 1533)      Branches: 0% (0 / 777)      Functions: 0% (0 / 537)      Lines: 6.46% (99 / 1532)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/user/
File Statements Branches Functions Lines
approval.js 6.38% (6 / 94) 0% (0 / 33) 0% (0 / 43) 6.38% (6 / 94)
auth.js 4.62% (3 / 65) 0% (0 / 33) 0% (0 / 25) 4.62% (3 / 65)
bans.js 4.08% (2 / 49) 0% (0 / 30) 0% (0 / 12) 4.08% (2 / 49)
categories.js 6.06% (2 / 33) 0% (0 / 12) 0% (0 / 15) 6.06% (2 / 33)
create.js 3.09% (3 / 97) 0% (0 / 60) 0% (0 / 35) 3.09% (3 / 97)
data.js 5.36% (6 / 112) 0% (0 / 81) 0% (0 / 30) 5.36% (6 / 112)
delete.js 9.89% (9 / 91) 0% (0 / 14) 0% (0 / 55) 9.89% (9 / 91)
digest.js 6.78% (4 / 59) 0% (0 / 35) 0% (0 / 19) 6.78% (4 / 59)
email.js 6.25% (3 / 48) 0% (0 / 20) 0% (0 / 20) 6.25% (3 / 48)
follow.js 9.76% (4 / 41) 0% (0 / 20) 0% (0 / 14) 9.76% (4 / 41)
info.js 8.22% (6 / 73) 0% (0 / 20) 0% (0 / 21) 8.22% (6 / 73)
invite.js 4.69% (3 / 64) 0% (0 / 21) 0% (0 / 33) 4.69% (3 / 64)
jobs.js 6.98% (3 / 43) 0% (0 / 10) 0% (0 / 5) 6.98% (3 / 43)
notifications.js 4.38% (6 / 137) 0% (0 / 75) 0% (0 / 46) 4.41% (6 / 136)
picture.js 9.01% (10 / 111) 0% (0 / 58) 0% (0 / 27) 9.01% (10 / 111)
posts.js 3.92% (2 / 51) 0% (0 / 33) 0% (0 / 17) 3.92% (2 / 51)
profile.js 10.32% (13 / 126) 0% (0 / 75) 0% (0 / 45) 10.32% (13 / 126)
reset.js 3.23% (2 / 62) 0% (0 / 24) 0% (0 / 31) 3.23% (2 / 62)
search.js 6.67% (6 / 90) 0% (0 / 58) 0% (0 / 23) 6.67% (6 / 90)
settings.js 5% (4 / 80) 0% (0 / 65) 0% (0 / 18) 5% (4 / 80)
topics.js 28.57% (2 / 7) 100% (0 / 0) 0% (0 / 3) 28.57% (2 / 7)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/approval.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/approval.js

Statements: 6.38% (6 / 94)      Branches: 0% (0 / 33)      Functions: 0% (0 / 43)      Lines: 6.38% (6 / 94)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227      2 2   2                                                                                 1                                                                                                                     1                                                 1                                                                                                                                                                                              
 
'use strict';
 
var async = require('async');
var request = require('request');
 
var db = require('../database');
var meta = require('../meta');
var emailer = require('../emailer');
var notifications = require('../notifications');
var groups = require('../groups');
var translator = require('../../public/src/modules/translator');
var utils = require('../../public/src/utils');
var plugins = require('../plugins');
 
module.exports = function (User) {
 
	User.addToApprovalQueue = function (userData, callback) {
		userData.userslug = utils.slugify(userData.username);
		async.waterfall([
			function (next) {
				User.isDataValid(userData, next);
			},
			function (next) {
				User.hashPassword(userData.password, next);
			},
			function (hashedPassword, next) {
				var data = {
					username: userData.username,
					email: userData.email,
					ip: userData.ip,
					hashedPassword: hashedPassword
				};
				plugins.fireHook('filter:user.addToApprovalQueue', {data: data, userData: userData}, next);
			},
			function (results, next) {
				db.setObject('registration:queue:name:' + userData.username, results.data, next);
			},
			function (next) {
				db.sortedSetAdd('registration:queue', Date.now(), userData.username, next);
			},
			function (next) {
				sendNotificationToAdmins(userData.username, next);
			}
		], callback);
	};
 
	function sendNotificationToAdmins(username, callback) {
		notifications.create({
			bodyShort: '[[notifications:new_register, ' + username + ']]',
			nid: 'new_register:' + username,
			path: '/admin/manage/registration',
			mergeId: 'new_register'
		}, function (err, notification) {
			if (err || !notification) {
				return callback(err);
			}
 
			notifications.pushGroup(notification, 'administrators', callback);
		});
	}
 
	User.acceptRegistration = function (username, callback) {
		var uid;
		var userData;
		async.waterfall([
			function (next) {
				db.getObject('registration:queue:name:' + username, next);
			},
			function (_userData, next) {
				if (!_userData) {
					return callback(new Error('[[error:invalid-data]]'));
				}
				userData = _userData;
				User.create(userData, next);
			},
			function (_uid, next) {
				uid = _uid;
				User.setUserField(uid, 'password', userData.hashedPassword, next);
			},
			function (next) {
				var title = meta.config.title || meta.config.browserTitle || 'NodeBB';
				translator.translate('[[email:welcome-to, ' + title + ']]', meta.config.defaultLang, function (subject) {
					var data = {
						site_title: title,
						username: username,
						subject: subject,
						template: 'registration_accepted',
						uid: uid
					};
 
					emailer.send('registration_accepted', uid, data, next);
				});
			},
			function (next) {
				removeFromQueue(username, next);
			},
			function (next) {
				markNotificationRead(username, next);
			},
			function (next) {
				next(null, uid);
			}
		], callback);
	};
 
	function markNotificationRead(username, callback) {
		var nid = 'new_register:' + username;
		async.waterfall([
			function (next) {
				groups.getMembers('administrators', 0, -1, next);
			},
			function (uids, next) {
				async.each(uids, function (uid, next) {
					notifications.markRead(nid, uid, next);
				}, next);
			}
		], callback);
	}
 
	User.rejectRegistration = function (username, callback) {
		async.waterfall([
			function (next) {
				removeFromQueue(username, next);
			},
			function (next) {
				markNotificationRead(username, next);
			}
		], callback);
	};
 
	function removeFromQueue(username, callback) {
		async.parallel([
			async.apply(db.sortedSetRemove, 'registration:queue', username),
			async.apply(db.delete, 'registration:queue:name:' + username)
		], function (err) {
			callback(err);
		});
	}
 
	User.getRegistrationQueue = function (start, stop, callback) {
		var data;
		async.waterfall([
			function (next) {
				db.getSortedSetRevRangeWithScores('registration:queue', start, stop, next);
			},
			function (_data, next) {
				data = _data;
				var keys = data.filter(Boolean).map(function (user) {
					return 'registration:queue:name:' + user.value;
				});
				db.getObjects(keys, next);
			},
			function (users, next) {
				users = users.map(function (user, index) {
					if (!user) {
						return null;
					}
 
					user.timestampISO = utils.toISOString(data[index].score);
					delete user.hashedPassword;
 
					return user;
				}).filter(Boolean);
 
				async.map(users, function (user, next) {
					if (!user) {
						return next(null, user);
					}
 
					// temporary: see http://www.stopforumspam.com/forum/viewtopic.php?id=6392
					user.ip = user.ip.replace('::ffff:', '');
 
					async.parallel([
						function (next) {
							User.getUidsFromSet('ip:' + user.ip + ':uid', 0, -1, function (err, uids) {
								if (err) {
									return next(err);
								}
 
								User.getUsersFields(uids, ['uid', 'username', 'picture'], function (err, ipMatch) {
									user.ipMatch = ipMatch;
									next(err);
								});
							});
						},
						function (next) {
							request({
								method: 'get',
								url: 'http://api.stopforumspam.org/api' +
									'?ip=' + encodeURIComponent(user.ip) +
									'&email=' + encodeURIComponent(user.email) +
									'&username=' + encodeURIComponent(user.username) +
									'&f=json',
								json: true
							}, function (err, response, body) {
								if (err) {
									return next();
								}
								if (response.statusCode === 200 && body) {
									user.spamData = body;
									user.usernameSpam = body.username ? (body.username.frequency > 0 || body.username.appears > 0) : true;
									user.emailSpam = body.email ? (body.email.frequency > 0 || body.email.appears > 0) : true;
									user.ipSpam = body.ip ? (body.ip.frequency > 0 || body.ip.appears > 0) : true;
								}
 
								next();
							});
						}
					], function (err) {
						next(err, user);
					});
				}, next);
			},
			function (users, next) {
				plugins.fireHook('filter:user.getRegistrationQueue', {users: users}, next);
			},
			function (results, next) {
				next(null, results.users);
			}
		], callback);
	};
 
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/auth.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/auth.js

Statements: 4.62% (3 / 65)      Branches: 0% (0 / 33)      Functions: 0% (0 / 25)      Lines: 4.62% (3 / 65)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146    2 2 2                                                                                                                                                                                                                                                                                          
'use strict';
 
var async = require('async');
var winston = require('winston');
var db = require('../database');
var meta = require('../meta');
var events = require('../events');
 
module.exports = function (User) {
	User.auth = {};
 
	User.auth.logAttempt = function (uid, ip, callback) {
		async.waterfall([
			function (next) {
				db.exists('lockout:' + uid, next);
			},
			function (exists, next) {
				if (exists) {
					return callback(new Error('[[error:account-locked]]'));
				}
				db.increment('loginAttempts:' + uid, next);
			},
			function (attemps, next) {
				var loginAttempts = parseInt(meta.config.loginAttempts, 10) || 5;
				if (attemps <= loginAttempts) {
					return db.pexpire('loginAttempts:' + uid, 1000 * 60 * 60, callback);
				}
				// Lock out the account
				db.set('lockout:' + uid, '', next);
			},
			function (next) {
				var duration = 1000 * 60 * (meta.config.lockoutDuration || 60);
 
				db.delete('loginAttempts:' + uid);
				db.pexpire('lockout:' + uid, duration);
				events.log({
					type: 'account-locked',
					uid: uid,
					ip: ip
				});
				next(new Error('[[error:account-locked]]'));
			}
		], callback);
	};
 
	User.auth.clearLoginAttempts = function (uid) {
		db.delete('loginAttempts:' + uid);
	};
 
	User.auth.resetLockout = function (uid, callback) {
		async.parallel([
			async.apply(db.delete, 'loginAttempts:' + uid),
			async.apply(db.delete, 'lockout:' + uid)
		], callback);
	};
 
	User.auth.getSessions = function (uid, curSessionId, callback) {
		var _sids;
 
		// curSessionId is optional
		if (arguments.length === 2 && typeof curSessionId === 'function') {
			callback = curSessionId;
			curSessionId = undefined;
		}
 
		async.waterfall([
			async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':sessions', 0, -1),
			function (sids, next) {
				_sids = sids;
				async.map(sids, db.sessionStore.get.bind(db.sessionStore), next);
			},
			function (sessions, next) {
				sessions.forEach(function (sessionObj, idx) {
					if (sessionObj && sessionObj.meta) {
						sessionObj.meta.current = curSessionId === _sids[idx];
					}
				});
 
				// Revoke any sessions that have expired, return filtered list
				var expiredSids = [],
					expired;
 
				sessions = sessions.filter(function (sessionObj, idx) {
					expired = !sessionObj || !sessionObj.hasOwnProperty('passport') ||
						!sessionObj.passport.hasOwnProperty('user')	||
						parseInt(sessionObj.passport.user, 10) !== parseInt(uid, 10);
 
					if (expired) {
						expiredSids.push(_sids[idx]);
					}
 
					return !expired;
				});
 
				async.each(expiredSids, function (sid, next) {
					User.auth.revokeSession(sid, uid, next);
				}, function (err) {
					next(err, sessions);
				});
			}
		], function (err, sessions) {
			callback(err, sessions ? sessions.map(function (sessObj) {
				sessObj.meta.datetimeISO = new Date(sessObj.meta.datetime).toISOString();
				return sessObj.meta;
			}) : undefined);
		});
	};
 
	User.auth.addSession = function (uid, sessionId, callback) {
		callback = callback || function () {};
		db.sortedSetAdd('uid:' + uid + ':sessions', Date.now(), sessionId, callback);
	};
 
	User.auth.revokeSession = function (sessionId, uid, callback) {
		winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid);
 
		db.sessionStore.get(sessionId, function (err, sessionObj) {
			if (err) {
				return callback(err);
			}
			async.parallel([
				function (next) {
					if (sessionObj && sessionObj.meta && sessionObj.meta.uuid) {
						db.deleteObjectField('uid:' + uid + ':sessionUUID:sessionId', sessionObj.meta.uuid, next);
					} else {
						next();
					}
				},
				async.apply(db.sortedSetRemove, 'uid:' + uid + ':sessions', sessionId),
				async.apply(db.sessionStore.destroy.bind(db.sessionStore), sessionId)
			], callback);
		});
	};
 
	User.auth.revokeAllSessions = function (uid, callback) {
		async.waterfall([
			async.apply(db.getSortedSetRange, 'uid:' + uid + ':sessions', 0, -1),
			function (sids, next) {
				async.each(sids, function (sid, next) {
					User.auth.revokeSession(sid, uid, next);
				}, next);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/bans.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/bans.js

Statements: 4.08% (2 / 49)      Branches: 0% (0 / 30)      Functions: 0% (0 / 12)      Lines: 4.08% (2 / 49)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110    2 2                                                                                                                                                                                                                    
'use strict';
 
var async = require('async');
var db = require('../database');
var plugins = require('../plugins');
 
module.exports = function (User) {
	User.ban = function (uid, until, reason, callback) {
		// "until" (optional) is unix timestamp in milliseconds
		// "reason" (optional) is a string
		if (!callback && typeof until === 'function') {
			callback = until;
			until = 0;
			reason = '';
		} else if (!callback && typeof reason === 'function') {
			callback = reason;
			reason = '';
		}
 
		var now = Date.now();
 
		until = parseInt(until, 10);
		if (isNaN(until)) {
			return callback(new Error('[[error:ban-expiry-missing]]'));
		}
 
		var tasks = [
			async.apply(User.setUserField, uid, 'banned', 1),
			async.apply(db.sortedSetAdd, 'users:banned', now, uid),
			async.apply(db.sortedSetAdd, 'uid:' + uid + ':bans', now, until)
		];
 
		if (until > 0 && now < until) {
			tasks.push(async.apply(db.sortedSetAdd, 'users:banned:expire', until, uid));
			tasks.push(async.apply(User.setUserField, uid, 'banned:expire', until));
		} else {
			until = 0;
		}
 
		if (reason) {
			tasks.push(async.apply(db.sortedSetAdd, 'banned:' + uid + ':reasons', now, reason));
		}
 
		async.series(tasks, function (err) {
			if (err) {
				return callback(err);
			}
 
			plugins.fireHook('action:user.banned', {
				uid: uid,
				until: until > 0 ? until : undefined
			});
			callback();
		});
	};
 
	User.unban = function (uid, callback) {
		async.waterfall([
			function (next) {
				User.setUserFields(uid, {banned: 0, 'banned:expire': 0}, next);
			},
			function (next) {
				db.sortedSetsRemove(['users:banned', 'users:banned:expire'], uid, next);
			},
			function (next) {
				plugins.fireHook('action:user.unbanned', {uid: uid});
				next();
			}
		], callback);
	};
 
	User.isBanned = function (uid, callback) {
		async.waterfall([
			async.apply(User.getUserFields, uid, ['banned', 'banned:expire']),
			function (userData, next) {
				var banned = parseInt(userData.banned, 10) === 1;
				if (!banned) {
					return next(null, banned);
				}
 
				// If they are banned, see if the ban has expired
				var stillBanned = !userData['banned:expire'] || Date.now() < userData['banned:expire'];
 
				if (stillBanned) {
					return next(null, true);
				}
				async.parallel([
					async.apply(db.sortedSetRemove.bind(db), 'users:banned:expire', uid),
					async.apply(db.sortedSetRemove.bind(db), 'users:banned', uid),
					async.apply(User.setUserFields, uid, {banned:0, 'banned:expire': 0})
				], function (err) {
					next(err, false);
				});
			}
		], callback);
	};
 
	User.getBannedReason = function (uid, callback) {
		// Grabs the latest ban reason
		db.getSortedSetRevRange('banned:' + uid + ':reasons', 0, 0, function (err, reasons) {
			if (err) {
				return callback(err);
			}
 
			callback(null, reasons.length ? reasons[0] : '');
		});
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/categories.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/categories.js

Statements: 6.06% (2 / 33)      Branches: 0% (0 / 12)      Functions: 0% (0 / 15)      Lines: 6.06% (2 / 33)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76    2   2                                                                                                                                              
'use strict';
 
var async = require('async');
 
var db = require('../database');
var categories = require('../categories');
 
module.exports = function (User) {
 
	User.getIgnoredCategories = function (uid, callback) {
		db.getSortedSetRange('uid:' + uid + ':ignored:cids', 0, -1, callback);
	};
 
	User.getWatchedCategories = function (uid, callback) {
		async.parallel({
			ignored: function (next) {
				User.getIgnoredCategories(uid, next);
			},
			all: function (next) {
				db.getSortedSetRange('categories:cid', 0, -1, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			var watched = results.all.filter(function (cid) {
				return cid && results.ignored.indexOf(cid) === -1;
			});
			callback(null, watched);
		});
	};
 
	User.ignoreCategory = function (uid, cid, callback) {
		if (!uid) {
			return callback();
		}
 
		async.waterfall([
			function (next) {
				categories.exists(cid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-category]]'));
				}
				db.sortedSetAdd('uid:' + uid + ':ignored:cids', Date.now(), cid, next);
			},
			function (next) {
				db.sortedSetAdd('cid:' + cid + ':ignorers', Date.now(), uid, next);
			}
		], callback);
	};
 
	User.watchCategory = function (uid, cid, callback) {
		if (!uid) {
			return callback();
		}
 
		async.waterfall([
			function (next) {
				categories.exists(cid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-category]]'));
				}
				db.sortedSetRemove('uid:' + uid + ':ignored:cids', cid, next);
			},
			function (next) {
				db.sortedSetRemove('cid:' + cid + ':ignorers', uid, next);
			}
		], callback);
	};
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/create.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/create.js

Statements: 3.09% (3 / 97)      Branches: 0% (0 / 60)      Functions: 0% (0 / 35)      Lines: 3.09% (3 / 97)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231    2 2                                                                                                                                                                                                                                                                                                                                                                                                               1                                                      
'use strict';
 
var async = require('async');
var db = require('../database');
var utils = require('../../public/src/utils');
var validator = require('validator');
var plugins = require('../plugins');
var groups = require('../groups');
var meta = require('../meta');
 
module.exports = function (User) {
 
	User.create = function (data, callback) {
		data.username = data.username.trim();
		data.userslug = utils.slugify(data.username);
		if (data.email !== undefined) {
			data.email = validator.escape(String(data.email).trim());
		}
 
		User.isDataValid(data, function (err) {
			if (err)  {
				return callback(err);
			}
			var timestamp = data.timestamp || Date.now();
 
			var userData = {
				'username': data.username,
				'userslug': data.userslug,
				'email': data.email || '',
				'joindate': timestamp,
				'lastonline': timestamp,
				'picture': '',
				'fullname': data.fullname || '',
				'location': '',
				'birthday': '',
				'website': '',
				'signature': '',
				'uploadedpicture': '',
				'profileviews': 0,
				'reputation': 0,
				'postcount': 0,
				'topiccount': 0,
				'lastposttime': 0,
				'banned': 0,
				'status': 'online'
			};
 
			async.parallel({
				renamedUsername: function (next) {
					renameUsername(userData, next);
				},
				userData: function (next) {
					plugins.fireHook('filter:user.create', {user: userData, data: data}, next);
				}
			}, function (err, results) {
				if (err) {
					return callback(err);
				}
 
				var userNameChanged = !!results.renamedUsername;
 
				if (userNameChanged) {
					userData.username = results.renamedUsername;
					userData.userslug = utils.slugify(results.renamedUsername);
				}
 
				async.waterfall([
					function (next) {
						db.incrObjectField('global', 'nextUid', next);
					},
					function (uid, next) {
						userData.uid = uid;
						db.setObject('user:' + uid, userData, next);
					},
					function (next) {
						async.parallel([
							function (next) {
								db.incrObjectField('global', 'userCount', next);
							},
							function (next) {
								db.sortedSetAdd('username:uid', userData.uid, userData.username, next);
							},
							function (next) {
								db.sortedSetAdd('username:sorted', 0, userData.username.toLowerCase() + ':' + userData.uid, next);
							},
							function (next) {
								db.sortedSetAdd('userslug:uid', userData.uid, userData.userslug, next);
							},
							function (next) {
								var sets = ['users:joindate', 'users:online'];
								if (parseInt(userData.uid) !== 1) {
									sets.push('users:notvalidated');
								}
								db.sortedSetsAdd(sets, timestamp, userData.uid, next);
							},
							function (next) {
								db.sortedSetsAdd(['users:postcount', 'users:reputation'], 0, userData.uid, next);
							},
							function (next) {
								groups.join('registered-users', userData.uid, next);
							},
							function (next) {
								User.notifications.sendWelcomeNotification(userData.uid, next);
							},
							function (next) {
								if (userData.email) {
									async.parallel([
										async.apply(db.sortedSetAdd, 'email:uid', userData.uid, userData.email.toLowerCase()),
										async.apply(db.sortedSetAdd, 'email:sorted', 0, userData.email.toLowerCase() + ':' + userData.uid)
									], next);
 
									if (parseInt(userData.uid, 10) !== 1 && parseInt(meta.config.requireEmailConfirmation, 10) === 1) {
										User.email.sendValidationEmail(userData.uid, userData.email);
									}
								} else {
									next();
								}
							},
							function (next) {
								if (!data.password) {
									return next();
								}
 
								User.hashPassword(data.password, function (err, hash) {
									if (err) {
										return next(err);
									}
 
									async.parallel([
										async.apply(User.setUserField, userData.uid, 'password', hash),
										async.apply(User.reset.updateExpiry, userData.uid)
									], next);
								});
							},
							function (next) {
								User.updateDigestSetting(userData.uid, meta.config.dailyDigestFreq, next);
							}
						], next);
					},
					function (results, next) {
						if (userNameChanged) {
							User.notifications.sendNameChangeNotification(userData.uid, userData.username);
						}
						plugins.fireHook('action:user.create', userData);
						next(null, userData.uid);
					}
				], callback);
			});
		});
	};
 
	User.isDataValid = function (userData, callback) {
		async.parallel({
			emailValid: function (next) {
				if (userData.email) {
					next(!utils.isEmailValid(userData.email) ? new Error('[[error:invalid-email]]') : null);
				} else {
					next();
				}
			},
			userNameValid: function (next) {
				next((!utils.isUserNameValid(userData.username) || !userData.userslug) ? new Error('[[error:invalid-username, ' + userData.username + ']]') : null);
			},
			passwordValid: function (next) {
				if (userData.password) {
					User.isPasswordValid(userData.password, next);
				} else {
					next();
				}
			},
			emailAvailable: function (next) {
				if (userData.email) {
					User.email.available(userData.email, function (err, available) {
						if (err) {
							return next(err);
						}
						next(!available ? new Error('[[error:email-taken]]') : null);
					});
				} else {
					next();
				}
			}
		}, function (err) {
			callback(err);
		});
	};
 
	User.isPasswordValid = function (password, callback) {
		if (!password || !utils.isPasswordValid(password)) {
			return callback(new Error('[[error:invalid-password]]'));
		}
 
		if (password.length < meta.config.minimumPasswordLength) {
			return callback(new Error('[[user:change_password_error_length]]'));
		}
 
		if (password.length > 4096) {
			return callback(new Error('[[error:password-too-long]]'));
		}
 
		callback();
	};
 
	function renameUsername(userData, callback) {
		meta.userOrGroupExists(userData.userslug, function (err, exists) {
			if (err || !exists) {
				return callback(err);
			}
 
			var	newUsername = '';
			async.forever(function (next) {
				newUsername = userData.username + (Math.floor(Math.random() * 255) + 1);
				User.existsBySlug(newUsername, function (err, exists) {
					if (err) {
						return callback(err);
					}
					if (!exists) {
						next(newUsername);
					} else {
						next();
					}
				});
			}, function (username) {
				callback(null, username);
			});
		});
	}
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/data.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/data.js

Statements: 5.36% (6 / 112)      Branches: 0% (0 / 81)      Functions: 0% (0 / 30)      Lines: 5.36% (6 / 112)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205    2 2 2   2                                             1                                                                                                                                   1                                                                                                                                                                                                                          
'use strict';
 
var validator = require('validator');
var nconf = require('nconf');
var winston = require('winston');
 
var db = require('../database');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
 
module.exports = function (User) {
 
	var iconBackgrounds = ['#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3',
		'#009688', '#1b5e20', '#33691e', '#827717', '#e65100', '#ff5722', '#795548', '#607d8b'];
 
	User.getUserField = function (uid, field, callback) {
		User.getUserFields(uid, [field], function (err, user) {
			callback(err, user ? user[field] : null);
		});
	};
 
	User.getUserFields = function (uid, fields, callback) {
		User.getUsersFields([uid], fields, function (err, users) {
			callback(err, users ? users[0] : null);
		});
	};
 
	User.getUsersFields = function (uids, fields, callback) {
		var fieldsToRemove = [];
		function addField(field) {
			if (fields.indexOf(field) === -1) {
				fields.push(field);
				fieldsToRemove.push(field);
			}
		}
 
		if (!Array.isArray(uids) || !uids.length) {
			return callback(null, []);
		}
 
		var keys = uids.map(function (uid) {
			return 'user:' + uid;
		});
 
		if (fields.indexOf('uid') === -1) {
			fields.push('uid');
		}
 
		if (fields.indexOf('picture') !== -1) {
			addField('email');
			addField('uploadedpicture');
		}
 
		if (fields.indexOf('status') !== -1) {
			addField('lastonline');
		}
 
		db.getObjectsFields(keys, fields, function (err, users) {
			if (err) {
				return callback(err);
			}
 
			modifyUserData(users, fieldsToRemove, callback);
		});
	};
 
	User.getMultipleUserFields = function (uids, fields, callback) {
		winston.warn('[deprecated] User.getMultipleUserFields is deprecated please use User.getUsersFields');
		User.getUsersFields(uids, fields, callback);
	};
 
	User.getUserData = function (uid, callback) {
		User.getUsersData([uid], function (err, users) {
			callback(err, users ? users[0] : null);
		});
	};
 
	User.getUsersData = function (uids, callback) {
		if (!Array.isArray(uids) || !uids.length) {
			return callback(null, []);
		}
 
		var keys = uids.map(function (uid) {
			return 'user:' + uid;
		});
 
		db.getObjects(keys, function (err, users) {
			if (err) {
				return callback(err);
			}
 
			modifyUserData(users, [], callback);
		});
	};
 
	function modifyUserData(users, fieldsToRemove, callback) {
		users.forEach(function (user) {
			if (!user) {
				return;
			}
 
			if (user.hasOwnProperty('username')) {
				user.username = validator.escape(user.username ? user.username.toString() : '');
			}
 
			if (user.password) {
				user.password = undefined;
			}
 
			if (!parseInt(user.uid, 10)) {
				user.uid = 0;
				user.username = '[[global:guest]]';
				user.userslug = '';
				user.picture = '';
				user['icon:text'] = '?';
				user['icon:bgColor'] = '#aaa';
			}
 
			if (user.picture && user.picture === user.uploadedpicture) {
				user.picture = user.uploadedpicture = user.picture.startsWith('http') ? user.picture : nconf.get('relative_path') + user.picture;
			} else if (user.uploadedpicture) {
				user.uploadedpicture = user.uploadedpicture.startsWith('http') ? user.uploadedpicture : nconf.get('relative_path') + user.uploadedpicture;
			}
 
			if (user.hasOwnProperty('status') && parseInt(user.lastonline, 10)) {
				user.status = User.getStatus(user);
			}
 
			for(var i = 0; i < fieldsToRemove.length; ++i) {
				user[fieldsToRemove[i]] = undefined;
			}
 
			// User Icons
			if (user.hasOwnProperty('picture') && user.username && parseInt(user.uid, 10)) {
				user['icon:text'] = (user.username[0] || '').toUpperCase();
				user['icon:bgColor'] = iconBackgrounds[Array.prototype.reduce.call(user.username, function (cur, next) {
					return cur + next.charCodeAt();
				}, 0) % iconBackgrounds.length];
			}
 
			if (user.hasOwnProperty('joindate')) {
				user.joindateISO = utils.toISOString(user.joindate);
			}
 
			if (user.hasOwnProperty('lastonline')) {
				user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO;
			}
		});
 
		plugins.fireHook('filter:users.get', users, callback);
	}
 
	User.setUserField = function (uid, field, value, callback) {
		callback = callback || function () {};
		db.setObjectField('user:' + uid, field, value, function (err) {
			if (err) {
				return callback(err);
			}
			plugins.fireHook('action:user.set', {uid: uid, field: field, value: value, type: 'set'});
			callback();
		});
	};
 
	User.setUserFields = function (uid, data, callback) {
		callback = callback || function () {};
		db.setObject('user:' + uid, data, function (err) {
			if (err) {
				return callback(err);
			}
			for (var field in data) {
				if (data.hasOwnProperty(field)) {
					plugins.fireHook('action:user.set', {uid: uid, field: field, value: data[field], type: 'set'});
				}
			}
			callback();
		});
	};
 
	User.incrementUserFieldBy = function (uid, field, value, callback) {
		callback = callback || function () {};
		db.incrObjectFieldBy('user:' + uid, field, value, function (err, value) {
			if (err) {
				return callback(err);
			}
			plugins.fireHook('action:user.set', {uid: uid, field: field, value: value, type: 'increment'});
 
			callback(null, value);
		});
	};
 
	User.decrementUserFieldBy = function (uid, field, value, callback) {
		callback = callback || function () {};
		db.incrObjectFieldBy('user:' + uid, field, -value, function (err, value) {
			if (err) {
				return callback(err);
			}
			plugins.fireHook('action:user.set', {uid: uid, field: field, value: value, type: 'decrement'});
 
			callback(null, value);
		});
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/delete.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/delete.js

Statements: 9.89% (9 / 91)      Branches: 0% (0 / 14)      Functions: 0% (0 / 55)      Lines: 9.89% (9 / 91)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247    2   2                                                     1               1                                                                                                                                                                                                                     1                                           1                                             1                                 1         1                                                                  
'use strict';
 
var async = require('async');
 
var db = require('../database');
var posts = require('../posts');
var topics = require('../topics');
var groups = require('../groups');
var plugins = require('../plugins');
var batch = require('../batch');
 
module.exports = function (User) {
 
	User.delete = function (callerUid, uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		async.waterfall([
			function (next) {
				deletePosts(callerUid, uid, next);
			},
			function (next) {
				deleteTopics(callerUid, uid, next);
			},
			function (next) {
				User.deleteAccount(uid, next);
			}
		], callback);
	};
 
	function deletePosts(callerUid, uid, callback) {
		batch.processSortedSet('uid:' + uid + ':posts', function (ids, next) {
			async.eachSeries(ids, function (pid, next) {
				posts.purge(pid, callerUid, next);
			}, next);
		}, {alwaysStartAt: 0}, callback);
	}
 
	function deleteTopics(callerUid, uid, callback) {
		batch.processSortedSet('uid:' + uid + ':topics', function (ids, next) {
			async.eachSeries(ids, function (tid, next) {
				topics.purge(tid, callerUid, next);
			}, next);
		}, {alwaysStartAt: 0}, callback);
	}
 
	User.deleteAccount = function (uid, callback) {
		var userData;
		async.waterfall([
			function (next) {
				User.exists(uid, next);
			},
			function (exists, next) {
				if (!exists) {
					return callback();
				}
				User.getUserFields(uid, ['username', 'userslug', 'fullname', 'email'], next);
			},
			function (_userData, next)  {
				userData = _userData;
				plugins.fireHook('static:user.delete', {uid: uid}, next);
			},
			function (next) {
				deleteVotes(uid, next);
			},
			function (next) {
				deleteChats(uid, next);
			},
			function (next) {
				User.auth.revokeAllSessions(uid, next);
			},
			function (next) {
				async.parallel([
					function (next) {
						db.sortedSetRemove('username:uid', userData.username, next);
					},
					function (next) {
						db.sortedSetRemove('username:sorted', userData.username.toLowerCase() + ':' + uid, next);
					},
					function (next) {
						db.sortedSetRemove('userslug:uid', userData.userslug, next);
					},
					function (next) {
						db.sortedSetRemove('fullname:uid', userData.fullname, next);
					},
					function (next) {
						if (userData.email) {
							async.parallel([
								async.apply(db.sortedSetRemove, 'email:uid', userData.email.toLowerCase()),
								async.apply(db.sortedSetRemove, 'email:sorted', userData.email.toLowerCase() + ':' + uid)
							], next);
						} else {
							next();
						}
					},
					function (next) {
						db.sortedSetsRemove([
							'users:joindate',
							'users:postcount',
							'users:reputation',
							'users:banned',
							'users:online',
							'users:notvalidated',
							'digest:day:uids',
							'digest:week:uids',
							'digest:month:uids'
						], uid, next);
					},
					function (next) {
						db.decrObjectField('global', 'userCount', next);
					},
					function (next) {
						var keys = [
							'uid:' + uid + ':notifications:read',
							'uid:' + uid + ':notifications:unread',
							'uid:' + uid + ':bookmarks',
							'uid:' + uid + ':followed_tids',
							'uid:' + uid + ':ignored_tids',
							'user:' + uid + ':settings',
							'uid:' + uid + ':topics', 'uid:' + uid + ':posts',
							'uid:' + uid + ':chats', 'uid:' + uid + ':chats:unread',
							'uid:' + uid + ':chat:rooms', 'uid:' + uid + ':chat:rooms:unread',
							'uid:' + uid + ':upvote', 'uid:' + uid + ':downvote',
							'uid:' + uid + ':ignored:cids', 'uid:' + uid + ':flag:pids',
							'uid:' + uid + ':sessions', 'uid:' + uid + ':sessionUUID:sessionId'
						];
						db.deleteAll(keys, next);
					},
					function (next) {
						deleteUserIps(uid, next);
					},
					function (next) {
						deleteUserFromFollowers(uid, next);
					},
					function (next) {
						groups.leaveAllGroups(uid, next);
					}
				], next);
			},
			function (results, next) {
				db.deleteAll(['followers:' + uid, 'following:' + uid, 'user:' + uid], next);
			}
		], callback);
	};
 
	function deleteVotes(uid, callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					upvotedPids: async.apply(db.getSortedSetRange, 'uid:' + uid + ':upvote', 0, -1),
					downvotedPids: async.apply(db.getSortedSetRange, 'uid:' + uid + ':downvote', 0, -1)
				}, next);
			},
			function (pids, next) {
				pids = pids.upvotedPids.concat(pids.downvotedPids).filter(function (pid, index, array) {
					return pid && array.indexOf(pid) === index;
				});
 
				async.eachSeries(pids, function (pid, next) {
					posts.unvote(pid, uid, next);
				}, next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	function deleteChats(uid, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('uid:' + uid + ':chat:rooms', 0, -1, next);
			},
			function (roomIds, next) {
				var userKeys = roomIds.map(function (roomId) {
					return 'uid:' + uid + ':chat:room:' + roomId + ':mids';
				});
				var roomKeys = roomIds.map(function (roomId) {
					return 'chat:room:' + roomId + ':uids';
				});
 
				async.parallel([
					async.apply(db.sortedSetsRemove, roomKeys, uid),
					async.apply(db.deleteAll, userKeys)
				], next);
			}
		], function (err) {
			callback(err);
		});
	}
 
	function deleteUserIps(uid, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('uid:' + uid + ':ip', 0, -1, next);
			},
			function (ips, next) {
				var keys = ips.map(function (ip) {
					return 'ip:' + ip + ':uid';
				});
				db.sortedSetsRemove(keys, uid, next);
			},
			function (next) {
				db.delete('uid:' + uid + ':ip', next);
			}
		], callback);
	}
 
	function deleteUserFromFollowers(uid, callback) {
		async.parallel({
			followers: async.apply(db.getSortedSetRange, 'followers:' + uid, 0, -1),
			following: async.apply(db.getSortedSetRange, 'following:' + uid, 0, -1)
		}, function (err, results) {
			function updateCount(uids, name, fieldName, next) {
				async.each(uids, function (uid, next) {
					db.sortedSetCard(name + uid, function (err, count) {
						if (err) {
							return next(err);
						}
						count = parseInt(count, 10) || 0;
						db.setObjectField('user:' + uid, fieldName, count, next);
					});
				}, next);
			}
 
			if (err) {
				return callback(err);
			}
 
			var followingSets = results.followers.map(function (uid) {
				return 'following:' + uid;
			});
 
			var followerSets = results.following.map(function (uid) {
				return 'followers:' + uid;
			});
 
			async.parallel([
				async.apply(db.sortedSetsRemove, followerSets.concat(followingSets), uid),
				async.apply(updateCount, results.following, 'followers:', 'followerCount'),
				async.apply(updateCount, results.followers, 'following:', 'followingCount')
			], callback);
		});
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/digest.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/digest.js

Statements: 6.78% (4 / 59)      Branches: 0% (0 / 35)      Functions: 0% (0 / 19)      Lines: 6.78% (4 / 59)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134    2 2 2   2                                                                                                                                                                                                                                                              
"use strict";
 
var async = require('async');
var winston = require('winston');
var nconf = require('nconf');
 
var db = require('../database');
var meta = require('../meta');
var user = require('../user');
var topics = require('../topics');
var plugins = require('../plugins');
var emailer = require('../emailer');
var utils = require('../../public/src/utils');
 
(function (Digest) {
	Digest.execute = function (interval, callback) {
		callback = callback || function () {};
 
		var digestsDisabled = parseInt(meta.config.disableEmailSubscriptions, 10) === 1;
		if (digestsDisabled) {
			winston.verbose('[user/jobs] Did not send digests (' + interval + ') because subscription system is disabled.');
			return callback();
		}
 
		if (!interval) {
			// interval is one of: day, week, month, or year
			interval = 'day';
		}
		var subscribers;
		async.waterfall([
			function (next) {
				async.parallel({
					topics: async.apply(topics.getLatestTopics, 0, 0, 9, interval),
					subscribers: async.apply(Digest.getSubscribers, interval)
				}, next);
			},
			function (data, next) {
				subscribers = data.subscribers;
				if (!data.subscribers.length) {
					return callback();
				}
 
				// Fix relative paths in topic data
				data.topics.topics = data.topics.topics.map(function (topicObj) {
					var user = topicObj.hasOwnProperty('teaser') && topicObj.teaser !== undefined ? topicObj.teaser.user : topicObj.user;
					if (user && user.picture && utils.isRelativeUrl(user.picture)) {
						user.picture = nconf.get('base_url') + user.picture;
					}
 
					return topicObj;
				});
 
				data.interval = interval;
				Digest.send(data, next);
			}
		], function (err) {
			if (err) {
				winston.error('[user/jobs] Could not send digests (' + interval + '): ' + err.message);
			} else {
				winston.verbose('[user/jobs] Digest (' + interval + ') scheduling completed. ' + subscribers.length + ' email(s) sent.');
			}
 
			callback(err);
		});
	};
 
	Digest.getSubscribers = function (interval, callback) {
		async.waterfall([
			function (next) {
				db.getSortedSetRange('digest:' + interval + ':uids', 0, -1, next);
			},
			function (subscribers, next) {
				plugins.fireHook('filter:digest.subscribers', {
					interval: interval,
					subscribers: subscribers
				}, next);
			},
			function (results, next) {
				next(null, results.subscribers);
			}
		], callback);
	};
 
	Digest.send = function (data, callback) {
		if (!data || !data.subscribers || !data.subscribers.length) {
			return callback();
		}
		var now = new Date();
 
		async.waterfall([
			function (next) {
				user.getUsersFields(data.subscribers, ['uid', 'username', 'userslug', 'lastonline'], next);
			},
			function (users, next) {
				async.eachLimit(users, 100, function (userObj, next) {
					async.waterfall([
						function (next) {
							user.notifications.getDailyUnread(userObj.uid, next);
						},
						function (notifications, next) {
							notifications = notifications.filter(Boolean);
							// If there are no notifications and no new topics, don't bother sending a digest
							if (!notifications.length && !data.topics.topics.length) {
								return next();
							}
 
							notifications.forEach(function (notification) {
								if (notification.image && !notification.image.startsWith('http')) {
									notification.image = nconf.get('url') + notification.image;
								}
							});
							emailer.send('digest', userObj.uid, {
								subject: '[' + meta.config.title + '] [[email:digest.subject, ' + (now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate()) + ']]',
								username: userObj.username,
								userslug: userObj.userslug,
								url: nconf.get('url'),
								site_title: meta.config.title || meta.config.browserTitle || 'NodeBB',
								notifications: notifications,
								recent: data.topics.topics,
								interval: data.interval
							});
							next();
						}
					], next);
				}, next);
			}
		], function (err) {
			callback(err);
		});
	};
 
}(module.exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/email.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/email.js

Statements: 6.25% (3 / 48)      Branches: 0% (0 / 20)      Functions: 0% (0 / 20)      Lines: 6.25% (3 / 48)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115      2 2   2                                                                                                                                                                                                                        
 
'use strict';
 
var async = require('async');
var nconf = require('nconf');
 
var user = require('../user');
var utils = require('../../public/src/utils');
var translator = require('../../public/src/modules/translator');
var plugins = require('../plugins');
var db = require('../database');
var meta = require('../meta');
var emailer = require('../emailer');
 
(function (UserEmail) {
 
	UserEmail.exists = function (email, callback) {
		user.getUidByEmail(email.toLowerCase(), function (err, exists) {
			callback(err, !!exists);
		});
	};
 
	UserEmail.available = function (email, callback) {
		db.isSortedSetMember('email:uid', email.toLowerCase(), function (err, exists) {
			callback(err, !exists);
		});
	};
 
	UserEmail.sendValidationEmail = function (uid, email, callback) {
		callback = callback || function () {};
		var confirm_code = utils.generateUUID();
		var confirm_link = nconf.get('url') + '/confirm/' + confirm_code;
 
		var emailInterval = meta.config.hasOwnProperty('emailConfirmInterval') ? parseInt(meta.config.emailConfirmInterval, 10) : 10;
 
		async.waterfall([
			function (next) {
				db.get('uid:' + uid + ':confirm:email:sent', next);
			},
			function (sent, next) {
				if (sent) {
					return next(new Error('[[error:confirm-email-already-sent, ' + emailInterval + ']]'));
				}
				db.set('uid:' + uid + ':confirm:email:sent', 1, next);
			},
			function (next) {
				db.pexpireAt('uid:' + uid + ':confirm:email:sent', Date.now() + (emailInterval * 60 * 1000), next);
			},
			function (next) {
				plugins.fireHook('filter:user.verify.code', confirm_code, next);
			},
			function (_confirm_code, next) {
				confirm_code = _confirm_code;
				db.setObject('confirm:' + confirm_code, {
					email: email.toLowerCase(),
					uid: uid
				}, next);
			},
			function (next) {
				db.expireAt('confirm:' + confirm_code, Math.floor(Date.now() / 1000 + 60 * 60 * 24), next);
			},
			function (next) {
				user.getUserField(uid, 'username', next);
			},
			function (username, next) {
				var title = meta.config.title || meta.config.browserTitle || 'NodeBB';
				translator.translate('[[email:welcome-to, ' + title + ']]', meta.config.defaultLang, function (subject) {
					var data = {
						site_title: title,
						username: username,
						confirm_link: confirm_link,
						confirm_code: confirm_code,
 
						subject: subject,
						template: 'welcome',
						uid: uid
					};
 
					if (plugins.hasListeners('action:user.verify')) {
						plugins.fireHook('action:user.verify', {uid: uid, data: data});
						next();
					} else {
						emailer.send('welcome', uid, data, next);
					}
				});
			}
		], callback);
	};
 
	UserEmail.confirm = function (code, callback) {
		db.getObject('confirm:' + code, function (err, confirmObj) {
			if (err) {
				return callback(new Error('[[error:parse-error]]'));
			}
 
			if (confirmObj && confirmObj.uid && confirmObj.email) {
				async.series([
					async.apply(user.setUserField, confirmObj.uid, 'email:confirmed', 1),
					async.apply(db.delete, 'confirm:' + code),
					async.apply(db.delete, 'uid:' + confirmObj.uid + ':confirm:email:sent'),
					function (next) {
						db.sortedSetRemove('users:notvalidated', confirmObj.uid, next);
					}
				], function (err) {
					callback(err ? new Error('[[error:email-confirm-failed]]') : null);
				});
			} else {
				callback(new Error('[[error:invalid-data]]'));
			}
		});
	};
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/follow.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/follow.js

Statements: 9.76% (4 / 41)      Branches: 0% (0 / 20)      Functions: 0% (0 / 14)      Lines: 9.76% (4 / 41)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103      2 2                         1                                                                                                           1                                                              
 
'use strict';
 
var async = require('async');
var plugins = require('../plugins');
var db = require('../database');
 
module.exports = function (User) {
 
	User.follow = function (uid, followuid, callback) {
		toggleFollow('follow', uid, followuid, callback);
	};
 
	User.unfollow = function (uid, unfollowuid, callback) {
		toggleFollow('unfollow', uid, unfollowuid, callback);
	};
 
	function toggleFollow(type, uid, theiruid, callback) {
		if (!parseInt(uid, 10) || !parseInt(theiruid, 10)) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		if (parseInt(uid, 10) === parseInt(theiruid, 10)) {
			return callback(new Error('[[error:you-cant-follow-yourself]]'));
		}
 
		async.waterfall([
			function (next) {
				User.exists(theiruid, next);
			},
			function (exists, next) {
				if (!exists) {
					return next(new Error('[[error:no-user]]'));
				}
				User.isFollowing(uid, theiruid, next);
			},
			function (isFollowing, next) {
				if (type === 'follow') {
					if (isFollowing) {
						return next(new Error('[[error:already-following]]'));
					}
					var now = Date.now();
					async.parallel([
						async.apply(db.sortedSetAdd, 'following:' + uid, now, theiruid),
						async.apply(db.sortedSetAdd, 'followers:' + theiruid, now, uid),
						async.apply(User.incrementUserFieldBy, uid, 'followingCount', 1),
						async.apply(User.incrementUserFieldBy, theiruid, 'followerCount', 1)
					], next);
				} else {
					if (!isFollowing) {
						return next(new Error('[[error:not-following]]'));
					}
					async.parallel([
						async.apply(db.sortedSetRemove, 'following:' + uid, theiruid),
						async.apply(db.sortedSetRemove, 'followers:' + theiruid, uid),
						async.apply(User.decrementUserFieldBy, uid, 'followingCount', 1),
						async.apply(User.decrementUserFieldBy, theiruid, 'followerCount', 1)
					], next);
				}
			}
		], callback);
	}
 
	User.getFollowing = function (uid, start, stop, callback) {
		getFollow(uid, 'following', start, stop, callback);
	};
 
	User.getFollowers = function (uid, start, stop, callback) {
		getFollow(uid, 'followers', start, stop, callback);
	};
 
	function getFollow(uid, type, start, stop, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, []);
		}
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange(type + ':' + uid, start, stop, next);
			},
			function (uids, next) {
				plugins.fireHook('filter:user.' + type, {
					uids: uids,
					uid: uid,
					start: start,
					stop: stop
				}, next);
			},
			function (data, next) {
				User.getUsers(data.uids, uid, next);
			}
		], callback);
	}
 
	User.isFollowing = function (uid, theirid, callback) {
		if (!parseInt(uid, 10) || !parseInt(theirid, 10)) {
			return callback(null, false);
		}
		db.isSortedSetMember('following:' + uid, theirid, callback);
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/info.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/info.js

Statements: 8.22% (6 / 73)      Branches: 0% (0 / 20)      Functions: 0% (0 / 21)      Lines: 8.22% (6 / 73)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141    2 2 2   2                                                                                                                                                       1                                                                     1                                              
'use strict';
 
var async = require('async');
var _ = require('underscore');
var validator = require('validator');
 
var db = require('../database');
var posts = require('../posts');
var topics = require('../topics');
 
module.exports = function (User) {
	User.getLatestBanInfo = function (uid, callback) {
		// Simply retrieves the last record of the user's ban, even if they've been unbanned since then.
		var timestamp, expiry, reason;
 
		async.waterfall([
			async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':bans', 0, 0),
			function (record, next) {
				if (!record.length) {
					return next(new Error('no-ban-info'));
				}
 
				timestamp = record[0].score;
				expiry = record[0].value;
 
				db.getSortedSetRangeByScore('banned:' + uid + ':reasons', 0, -1, timestamp, timestamp, next);
			},
			function (_reason, next) {
				reason = _reason && _reason.length ? _reason[0] : '';
				next();
			}
		], function (err) {
			if (err) {
				return callback(err);
			}
 
			callback(null, {
				uid: uid,
				timestamp: timestamp,
				expiry: parseInt(expiry, 10),
				expiry_readable: new Date(parseInt(expiry, 10)).toString().replace(/:/g, '%3A'),
				reason: validator.escape(String(reason))
			});
		});
	};
 
	User.getModerationHistory = function (uid, callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					flags: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':flag:pids', 0, 19),
					bans: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':bans', 0, 19),
					reasons: async.apply(db.getSortedSetRevRangeWithScores, 'banned:' + uid + ':reasons', 0, 19)
				}, next);
			},
			function (data, next) {
				getFlagMetadata(data, next);
			}
		], function (err, data) {
			if (err) {
				return callback(err);
			}
			formatBanData(data);
			callback(null, data);
		});
	};
 
	User.getHistory = function (set, callback) {
		db.getSortedSetRevRangeWithScores(set, 0, -1, function (err, data) {
			if (err) {
				return callback(err);
			}
			callback(null, data.map(function (set) {
				set.timestamp = set.score;
				set.timestampISO = new Date(set.score).toISOString();
				set.value = validator.escape(String(set.value.split(':')[0]));
				delete set.score;
				return set;
			}));
		});
	};
 
	function getFlagMetadata(data, callback) {
		var pids = data.flags.map(function (flagObj) {
			return parseInt(flagObj.value, 10);
		});
 
		posts.getPostsFields(pids, ['tid'], function (err, postData) {
			if (err) {
				return callback(err);
			}
 
			var tids = postData.map(function (post) {
				return post.tid;
			});
 
			topics.getTopicsFields(tids, ['title'], function (err, topicData) {
				if (err) {
					return callback(err);
				}
				data.flags = data.flags.map(function (flagObj, idx) {
					flagObj.pid = flagObj.value;
					flagObj.timestamp = flagObj.score;
					flagObj.timestampISO = new Date(flagObj.score).toISOString();
					flagObj.timestampReadable = new Date(flagObj.score).toString();
 
					delete flagObj.value;
					delete flagObj.score;
 
					return _.extend(flagObj, topicData[idx]);
				});
 
				callback(null, data);
			});
		});
	}
 
	function formatBanData(data) {
		var reasons = data.reasons.reduce(function (memo, cur) {
				memo[cur.score] = cur.value;
				return memo;
			}, {});
 
		data.bans = data.bans.map(function (banObj) {
			banObj.until = parseInt(banObj.value, 10);
			banObj.untilReadable = new Date(banObj.until).toString();
			banObj.timestamp = parseInt(banObj.score, 10);
			banObj.timestampReadable = new Date(banObj.score).toString();
			banObj.timestampISO = new Date(banObj.score).toISOString();
			banObj.reason = validator.escape(String(reasons[banObj.score] || '')) || '[[user:info.banned-no-reason]]';
 
			delete banObj.value;
			delete banObj.score;
			delete data.reasons;
 
			return banObj;
		});
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/invite.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/invite.js

Statements: 4.69% (3 / 64)      Branches: 0% (0 / 21)      Functions: 0% (0 / 33)      Lines: 4.69% (3 / 64)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154      2 2   2                                                                                                                                                                                                                                                                                                      
 
'use strict';
 
var async = require('async');
var nconf = require('nconf');
 
var db = require('./../database');
var meta = require('../meta');
var emailer = require('../emailer');
var translator = require('../../public/src/modules/translator');
var utils = require('../../public/src/utils');
 
 
module.exports = function (User) {
 
	User.getInvites = function (uid, callback) {
		db.getSetMembers('invitation:uid:' + uid, callback);
	};
 
	User.getInvitesNumber = function (uid, callback) {
		db.setCount('invitation:uid:' + uid, callback);
	};
 
	User.getInvitingUsers = function (callback) {
		db.getSetMembers('invitation:uids', callback);
	};
 
	User.getAllInvites = function (callback) {
		var uids;
		async.waterfall([
			User.getInvitingUsers,
			function (_uids, next) {
				uids = _uids;
				async.map(uids, User.getInvites, next);
			},
			function (invitations, next) {
				invitations = invitations.map(function (invites, index) {
					return {
						uid: uids[index],
						invitations: invites
					};
				});
				next(null, invitations);
			}
		], callback);
	};
 
	User.sendInvitationEmail = function (uid, email, callback) {
		callback = callback || function () {};
 
		var token = utils.generateUUID();
		var registerLink = nconf.get('url') + '/register?token=' + token + '&email=' + encodeURIComponent(email);
 
		var oneDay = 86400000;
 
		async.waterfall([
			function (next) {
				User.getUidByEmail(email, next);
			},
			function (exists, next) {
				if (exists) {
					return next(new Error('[[error:email-taken]]'));
				}
				next();
			},
			function (next) {
				async.parallel([
					function (next) {
						db.setAdd('invitation:uid:' + uid, email, next);
					},
					function (next) {
						db.setAdd('invitation:uids', uid, next);
					}
				], function (err) {
					next(err);
				});
			},
			function (next) {
				db.set('invitation:email:' + email, token, next);
			},
			function (next) {
				db.pexpireAt('invitation:email:' + email, Date.now() + oneDay, next);
			},
			function (next) {
				User.getUserField(uid, 'username', next);
			},
			function (username, next) {
				var title = meta.config.title || meta.config.browserTitle || 'NodeBB';
				translator.translate('[[email:invite, ' + title + ']]', meta.config.defaultLang, function (subject) {
					var data = {
						site_title: title,
						registerLink: registerLink,
						subject: subject,
						username: username,
						template: 'invitation'
					};
 
					emailer.sendToEmail('invitation', email, meta.config.defaultLang, data, next);
				});
			}
		], callback);
	};
 
	User.verifyInvitation = function (query, callback) {
		if (!query.token || !query.email) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				db.get('invitation:email:' + query.email, next);
			},
			function (token, next) {
				if (!token || token !== query.token) {
					return next(new Error('[[error:invalid-token]]'));
				}
 
				next();
			}
		], callback);
	};
 
	User.deleteInvitation = function (invitedBy, email, callback) {
		callback = callback || function () {};
		async.waterfall([
			function getInvitedByUid(next) {
				User.getUidByUsername(invitedBy, next);
			},
			function deleteRegistries(invitedByUid, next) {
				if (!invitedByUid) {
					return next(new Error('[[error:invalid-username]]'));
				}
				async.parallel([
					function deleteFromReferenceList(next) {
						db.setRemove('invitation:uid:' + invitedByUid, email, next);
					},
					function deleteInviteKey(next) {
						db.delete('invitation:email:' + email, callback);
					}
				], function (err) {
					next(err);
				});
			}
		], callback);
	};
 
	User.deleteInvitationKey = function (email, callback) {
		callback = callback || function () {};
		db.delete('invitation:email:' + email, callback);
	};
 
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/jobs.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/jobs.js

Statements: 6.98% (3 / 43)      Branches: 0% (0 / 10)      Functions: 0% (0 / 5)      Lines: 6.98% (3 / 43)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71    2 2   2                                                                                                                                  
'use strict';
 
var winston = require('winston');
var cronJob = require('cron').CronJob;
 
var meta = require('../meta');
 
var jobs = {};
 
module.exports = function (User) {
	User.startJobs = function (callback) {
		winston.verbose('[user/jobs] (Re-)starting user jobs...');
		var terminated = 0;
		var started = 0;
		var digestHour = parseInt(meta.config.digestHour, 10);
 
		// Fix digest hour if invalid
		if (isNaN(digestHour)) {
			digestHour = 17;
		} else if (digestHour > 23 || digestHour < 0) {
			digestHour = 0;
		}
 
		// Terminate any active cron jobs
		for(var jobId in jobs) {
			if (jobs.hasOwnProperty(jobId)) {
				winston.verbose('[user/jobs] Terminating job (' + jobId + ')');
				jobs[jobId].stop();
				delete jobs[jobId];
				++terminated;
			}
		}
		winston.verbose('[user/jobs] ' + terminated + ' jobs terminated');
 
		jobs['digest.daily'] = new cronJob('0 0 ' + digestHour + ' * * *', function () {
			winston.verbose('[user/jobs] Digest job (daily) started.');
			User.digest.execute('day');
		}, null, true);
		winston.verbose('[user/jobs] Starting job (digest.daily)');
		++started;
 
		jobs['digest.weekly'] = new cronJob('0 0 ' + digestHour + ' * * 0', function () {
			winston.verbose('[user/jobs] Digest job (weekly) started.');
			User.digest.execute('week');
		}, null, true);
		winston.verbose('[user/jobs] Starting job (digest.weekly)');
		++started;
 
		jobs['digest.monthly'] = new cronJob('0 0 ' + digestHour + ' 1 * *', function () {
			winston.verbose('[user/jobs] Digest job (monthly) started.');
			User.digest.execute('month');
		}, null, true);
		winston.verbose('[user/jobs] Starting job (digest.monthly)');
		++started;
 
		jobs['reset.clean'] = new cronJob('0 0 0 * * *', User.reset.clean, null, true);
		winston.verbose('[user/jobs] Starting job (reset.clean)');
		++started;
 
		winston.verbose('[user/jobs] ' + started + ' jobs started');
 
		if (typeof callback === 'function') {
			callback();
		}
 
		return;
	};
};
 
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/notifications.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/notifications.js

Statements: 4.38% (6 / 137)      Branches: 0% (0 / 75)      Functions: 0% (0 / 46)      Lines: 4.41% (6 / 136)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293      2 2 2   2                                                                                   1                     1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
 
'use strict';
 
var async = require('async');
var winston = require('winston');
var S = require('string');
 
var db = require('../database');
var meta = require('../meta');
var notifications = require('../notifications');
var privileges = require('../privileges');
 
(function (UserNotifications) {
 
	UserNotifications.get = function (uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null , {read: [], unread: []});
		}
		getNotifications(uid, 0, 9, function (err, notifications) {
			if (err) {
				return callback(err);
			}
 
			notifications.read = notifications.read.filter(Boolean);
			notifications.unread = notifications.unread.filter(Boolean);
 
			var maxNotifs = 15;
			if (notifications.read.length + notifications.unread.length > maxNotifs) {
				notifications.read.length = maxNotifs - notifications.unread.length;
			}
 
			callback(null, notifications);
		});
	};
 
	UserNotifications.getAll = function (uid, start, stop, callback) {
		getNotifications(uid, start, stop, function (err, notifs) {
			if (err) {
				return callback(err);
			}
			notifs = notifs.unread.concat(notifs.read);
			notifs = notifs.filter(Boolean).sort(function (a, b) {
				return b.datetime - a.datetime;
			});
 
			callback(null, notifs);
		});
	};
 
	function getNotifications(uid, start, stop, callback) {
		async.parallel({
			unread: function (next) {
				getNotificationsFromSet('uid:' + uid + ':notifications:unread', false, uid, start, stop, next);
			},
			read: function (next) {
				getNotificationsFromSet('uid:' + uid + ':notifications:read', true, uid, start, stop, next);
			}
		}, callback);
	}
 
	function getNotificationsFromSet(set, read, uid, start, stop, callback) {
		var setNids;
 
		async.waterfall([
			async.apply(db.getSortedSetRevRange, set, start, stop),
			function (nids, next) {
				if(!Array.isArray(nids) || !nids.length) {
					return callback(null, []);
				}
 
				setNids = nids;
				UserNotifications.getNotifications(nids, uid, next);
			},
			function (notifs, next) {
				var deletedNids = [];
 
				notifs.forEach(function (notification, index) {
					if (!notification) {
						winston.verbose('[notifications.get] nid ' + setNids[index] + ' not found. Removing.');
						deletedNids.push(setNids[index]);
					} else {
						notification.read = read;
						notification.readClass = !notification.read ? 'unread' : '';
					}
				});
 
				if (deletedNids.length) {
					db.sortedSetRemove(set, deletedNids);
				}
 
				notifications.merge(notifs, next);
			}
		], callback);
	}
 
	UserNotifications.getNotifications = function (nids, uid, callback) {
		notifications.getMultiple(nids, function (err, notifications) {
			if (err) {
				return callback(err);
			}
			notifications = notifications.filter(function (notification) {
				return notification && notification.path;
			});
			callback(null, notifications);
		});
	};
 
 
	UserNotifications.getDailyUnread = function (uid, callback) {
		var yesterday = Date.now() - (1000 * 60 * 60 * 24);	// Approximate, can be more or less depending on time changes, makes no difference really.
 
		db.getSortedSetRevRangeByScore('uid:' + uid + ':notifications:unread', 0, 20, '+inf', yesterday, function (err, nids) {
			if (err) {
				return callback(err);
			}
 
			if (!Array.isArray(nids) || !nids.length) {
				return callback(null, []);
			}
 
			UserNotifications.getNotifications(nids, uid, callback);
		});
	};
 
	UserNotifications.getUnreadCount = function (uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback(null, 0);
		}
 
		// Collapse any notifications with identical mergeIds
		async.waterfall([
			async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':notifications:unread', 0, 99),
			async.apply(notifications.filterExists),
			function (nids, next) {
				var keys = nids.map(function (nid) {
					return 'notifications:' + nid;
				});
 
				db.getObjectsFields(keys, ['mergeId'], next);
			}
		], function (err, mergeIds) {
			// A missing (null) mergeId means that notification is counted separately.
			mergeIds = mergeIds.map(function (set) {
				return set.mergeId;
			});
 
			callback(err, mergeIds.reduce(function (count, cur, idx, arr) {
				if (cur === null || idx === arr.indexOf(cur)) {
					++count;
				}
 
				return count;
			}, 0));
		});
	};
 
	UserNotifications.getUnreadByField = function (uid, field, values, callback) {
		db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, function (err, nids) {
			if (err) {
				return callback(err);
			}
 
			if (!Array.isArray(nids) || !nids.length) {
				return callback(null, []);
			}
 
			var keys = nids.map(function (nid) {
				return 'notifications:' + nid;
			});
 
			db.getObjectsFields(keys, ['nid', field], function (err, notifications) {
				if (err) {
					return callback(err);
				}
 
				values = values.map(function () { return values.toString(); });
				nids = notifications.filter(function (notification) {
					return notification && notification[field] && values.indexOf(notification[field].toString()) !== -1;
				}).map(function (notification) {
					return notification.nid;
				});
 
				callback(null, nids);
			});
		});
	};
 
	UserNotifications.deleteAll = function (uid, callback) {
		if (!parseInt(uid, 10)) {
			return callback();
		}
		async.parallel([
			function (next) {
				db.delete('uid:' + uid + ':notifications:unread', next);
			},
			function (next) {
				db.delete('uid:' + uid + ':notifications:read', next);
			}
		], callback);
	};
 
	UserNotifications.sendTopicNotificationToFollowers = function (uid, topicData, postData) {
		var followers;
		async.waterfall([
			function (next) {
				db.getSortedSetRange('followers:' + uid, 0, -1, next);
			},
			function (followers, next) {
				if (!Array.isArray(followers) || !followers.length) {
					return;
				}
				privileges.categories.filterUids('read', topicData.cid, followers, next);
			},
			function (_followers, next) {
				followers = _followers;
				if (!followers.length) {
					return;
				}
 
				var title = topicData.title;
				if (title) {
					title = S(title).decodeHTMLEntities().s;
				}
 
				notifications.create({
					bodyShort: '[[notifications:user_posted_topic, ' + postData.user.username + ', ' + title + ']]',
					bodyLong: postData.content,
					pid: postData.pid,
					path: '/post/' + postData.pid,
					nid: 'tid:' + postData.tid + ':uid:' + uid,
					tid: postData.tid,
					from: uid
				}, next);
			}
		], function (err, notification) {
			if (err) {
				return winston.error(err);
			}
 
			if (notification) {
				notifications.push(notification, followers);
			}
		});
	};
 
	UserNotifications.sendWelcomeNotification = function (uid, callback) {
		callback = callback || function () {};
		if (!meta.config.welcomeNotification) {
			return callback();
		}
 
		var path = meta.config.welcomeLink ? meta.config.welcomeLink : '#';
 
		notifications.create({
			bodyShort: meta.config.welcomeNotification,
			path: path,
			nid: 'welcome_' + uid
		}, function (err, notification) {
			if (err || !notification) {
				return callback(err);
			}
 
			notifications.push(notification, [uid], callback);
		});
	};
 
	UserNotifications.sendNameChangeNotification = function (uid, username) {
		notifications.create({
			bodyShort: '[[user:username_taken_workaround, ' + username + ']]',
			image: 'brand:logo',
			nid: 'username_taken:' + uid,
			datetime: Date.now()
		}, function (err, notification) {
			if (!err && notification) {
				notifications.push(notification, uid);
			}
		});
	};
 
	UserNotifications.pushCount = function (uid) {
		var websockets = require('./../socket.io');
		UserNotifications.getUnreadCount(uid, function (err, count) {
			if (err) {
				return winston.error(err.stack);
			}
 
			websockets.in('uid_' + uid).emit('event:notifications.updateCount', count);
		});
	};
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/picture.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/picture.js

Statements: 9.01% (10 / 111)      Branches: 0% (0 / 58)      Functions: 0% (0 / 27)      Lines: 9.01% (10 / 111)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229    2 2 2 2 2 2 2 2 2   2                                                                                                                                                                                                                                                                                                                                                                                                                                                
'use strict';
 
var async = require('async');
var path = require('path');
var fs = require('fs');
var os = require('os');
var nconf = require('nconf');
var crypto = require('crypto');
var winston = require('winston');
var request = require('request');
var mime = require('mime');
 
var plugins = require('../plugins');
var file = require('../file');
var image = require('../image');
var meta = require('../meta');
var db = require('../database');
 
module.exports = function (User) {
 
	User.uploadPicture = function (uid, picture, callback) {
 
		var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256;
		var extension = path.extname(picture.name);
		var updateUid = uid;
		var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128;
		var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1;
		var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1;
		var uploadedImage;
 
		if (parseInt(meta.config.allowProfileImageUploads) !== 1) {
			return callback(new Error('[[error:profile-image-uploads-disabled]]'));
		}
 
		if (picture.size > uploadSize * 1024) {
			return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]'));
		}
 
		if (!extension) {
			return callback(new Error('[[error:invalid-image-extension]]'));
		}
 
		async.waterfall([
			function (next) {
				if (plugins.hasListeners('filter:uploadImage')) {
					return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, next);
				}
 
				var filename = updateUid + '-profileimg' + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension);
 
				async.waterfall([
					function (next) {
						file.isFileTypeAllowed(picture.path, next);
					},
					function (next) {
						image.resizeImage({
							path: picture.path,
							extension: extension,
							width: imageDimension,
							height: imageDimension
						}, next);
					},
					function (next) {
						if (!convertToPNG) {
							return next();
						}
						async.series([
							async.apply(image.normalise, picture.path, extension),
							async.apply(fs.rename, picture.path + '.png', picture.path)
						], function (err) {
							next(err);
						});
					},
					function (next) {
						file.saveFileToLocal(filename, 'profile', picture.path, next);
					},
				], next);
			},
			function (_image, next) {
				uploadedImage = _image;
				User.setUserFields(updateUid, {uploadedpicture: uploadedImage.url, picture: uploadedImage.url}, next);
			},
			function (next) {
				next(null, uploadedImage);
			}
		], callback);
	};
 
	User.uploadFromUrl = function (uid, url, callback) {
		if (!plugins.hasListeners('filter:uploadImage')) {
			return callback(new Error('[[error:no-plugin]]'));
		}
 
		request.head(url, function (err, res) {
			if (err) {
				return callback(err);
			}
			var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256;
			var size = res.headers['content-length'];
			var type = res.headers['content-type'];
			var extension = mime.extension(type);
 
			if (['png', 'jpeg', 'jpg', 'gif'].indexOf(extension) === -1) {
				return callback(new Error('[[error:invalid-image-extension]]'));
			}
 
			if (size > uploadSize * 1024) {
				return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]'));
			}
 
			var picture = {url: url, name: ''};
			plugins.fireHook('filter:uploadImage', {image: picture, uid: uid}, function (err, image) {
				if (err) {
					return callback(err);
				}
				User.setUserFields(uid, {uploadedpicture: image.url, picture: image.url});
				callback(null, image);
			});
		});
	};
 
	User.updateCoverPosition = function (uid, position, callback) {
		User.setUserField(uid, 'cover:position', position, callback);
	};
 
	User.updateCoverPicture = function (data, callback) {
		var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1;
		var url, md5sum;
 
		if (!data.imageData && data.position) {
			return User.updateCoverPosition(data.uid, data.position, callback);
		}
 
		if (!data.imageData && !data.file) {
			return callback(new Error('[[error:invalid-data]]'));
		}
 
		async.waterfall([
			function (next) {
				var size = data.file ? data.file.size : data.imageData.length;
				meta.config.maximumCoverImageSize = meta.config.maximumCoverImageSize || 2048;
				if (size > parseInt(meta.config.maximumCoverImageSize, 10) * 1024) {
					return next(new Error('[[error:file-too-big, ' + meta.config.maximumCoverImageSize + ']]'));
				}
 
				if (data.file) {
					return next();
				}
 
				md5sum = crypto.createHash('md5');
				md5sum.update(data.imageData);
				md5sum = md5sum.digest('hex');
 
				data.file = {
					path: path.join(os.tmpdir(), md5sum)
				};
 
				var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64');
 
				fs.writeFile(data.file.path, buffer, {
					encoding: 'base64'
				}, next);
			},
			function (next) {
				var image = {
					name: 'profileCover',
					path: data.file.path,
					uid: data.uid
				};
 
				if (plugins.hasListeners('filter:uploadImage')) {
					return plugins.fireHook('filter:uploadImage', {image: image, uid: data.uid}, next);
				}
 
				var filename = data.uid + '-profilecover' + (keepAllVersions ? '-' + Date.now() : '');
				async.waterfall([
					function (next) {
						file.isFileTypeAllowed(data.file.path, next);
					},
					function (next) {
						file.saveFileToLocal(filename, 'profile', image.path, next);
					},
					function (upload, next) {
						next(null, {
							url: nconf.get('relative_path') + upload.url,
							name: image.name
						});
					}
				], next);
			},
			function (uploadData, next) {
				url = uploadData.url;
				User.setUserField(data.uid, 'cover:url', uploadData.url, next);
			},
			function (next) {
				fs.unlink(data.file.path, function (err) {
					if (err) {
						winston.error(err);
					}
					next();
				});
			}
		], function (err) {
			if (err) {
				return fs.unlink(data.file.path, function (unlinkErr) {
					if (unlinkErr) {
						winston.error(unlinkErr);
					}
 
					callback(err);	// send back the original error
				});
			}
 
			if (data.position) {
				User.updateCoverPosition(data.uid, data.position, function (err) {
					callback(err, {url: url});
				});
			} else {
				callback(err, {url: url});
			}
		});
	};
 
	User.removeCoverPicture = function (data, callback) {
		db.deleteObjectField('user:' + data.uid, 'cover:url', callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/posts.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/posts.js

Statements: 3.92% (2 / 51)      Branches: 0% (0 / 33)      Functions: 0% (0 / 17)      Lines: 3.92% (2 / 51)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106    2 2                                                                                                                                                                                                            
'use strict';
 
var async = require('async');
var db = require('../database');
var meta = require('../meta');
var privileges = require('../privileges');
 
module.exports = function (User) {
 
	User.isReadyToPost = function (uid, cid, callback) {
		if (parseInt(uid, 10) === 0) {
			return callback();
		}
 
		async.parallel({
			userData: function (next) {
				User.getUserFields(uid, ['banned', 'lastposttime', 'joindate', 'email', 'email:confirmed', 'reputation'], next);
			},
			exists: function (next) {
				db.exists('user:' + uid, next);
			},
			isAdminOrMod: function (next) {
				privileges.categories.isAdminOrMod(cid, uid, next);
			}
		}, function (err, results) {
			if (err) {
				return callback(err);
			}
 
			if (!results.exists) {
				return callback(new Error('[[error:no-user]]'));
			}
 
			if (results.isAdminOrMod) {
				return callback();
			}
 
			var userData = results.userData;
 
			if (parseInt(userData.banned, 10) === 1) {
				return callback(new Error('[[error:user-banned]]'));
			}
 
			if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) {
				return callback(new Error('[[error:email-not-confirmed]]'));
			}
 
			var now = Date.now();
			if (now - parseInt(userData.joindate, 10) < parseInt(meta.config.initialPostDelay, 10) * 1000) {
				return callback(new Error('[[error:user-too-new, ' + meta.config.initialPostDelay + ']]'));
			}
 
			var lastposttime = userData.lastposttime || 0;
 
			if (parseInt(meta.config.newbiePostDelay, 10) > 0 && parseInt(meta.config.newbiePostDelayThreshold, 10) > parseInt(userData.reputation, 10) && now - parseInt(lastposttime, 10) < parseInt(meta.config.newbiePostDelay, 10) * 1000) {
				return callback(new Error('[[error:too-many-posts-newbie, ' + meta.config.newbiePostDelay + ', ' + meta.config.newbiePostDelayThreshold + ']]'));
			} else if (now - parseInt(lastposttime, 10) < parseInt(meta.config.postDelay, 10) * 1000) {
				return callback(new Error('[[error:too-many-posts, ' + meta.config.postDelay + ']]'));
			}
 
			callback();
		});
	};
 
	User.onNewPostMade = function (postData, callback) {
		async.series([
			function (next) {
				User.addPostIdToUser(postData.uid, postData.pid, postData.timestamp, next);
			},
			function (next) {
				User.incrementUserPostCountBy(postData.uid, 1, next);
			},
			function (next) {
				User.setUserField(postData.uid, 'lastposttime', postData.timestamp, next);
			},
			function (next) {
				User.updateLastOnlineTime(postData.uid, next);
			}
		], callback);
	};
 
	User.addPostIdToUser = function (uid, pid, timestamp, callback) {
		db.sortedSetAdd('uid:' + uid + ':posts', timestamp, pid, callback);
	};
 
	User.incrementUserPostCountBy = function (uid, value, callback) {
		callback = callback || function () {};
		User.incrementUserFieldBy(uid, 'postcount', value, function (err, newpostcount) {
			if (err) {
				return callback(err);
			}
			if (!parseInt(uid, 10)) {
				return callback();
			}
			db.sortedSetAdd('users:postcount', newpostcount, uid, callback);
		});
	};
 
	User.getPostIds = function (uid, start, stop, callback) {
		db.getSortedSetRevRange('uid:' + uid + ':posts', start, stop, function (err, pids) {
			callback(err, Array.isArray(pids) ? pids : []);
		});
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/profile.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/profile.js

Statements: 10.32% (13 / 126)      Branches: 0% (0 / 75)      Functions: 0% (0 / 45)      Lines: 10.32% (13 / 126)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290      2 2   2 2                                                                                                                 1               1               1                                                 1                                                                     1               1                                                                                       1                                                         1                                           1                                                                                            
 
'use strict';
 
var async = require('async');
var S = require('string');
 
var utils = require('../../public/src/utils');
var meta = require('../meta');
var db = require('../database');
var groups = require('../groups');
var plugins = require('../plugins');
 
module.exports = function (User) {
 
	User.updateProfile = function (uid, data, callback) {
		var fields = ['username', 'email', 'fullname', 'website', 'location',
			'groupTitle', 'birthday', 'signature', 'aboutme', 'picture', 'uploadedpicture'];
 
		async.waterfall([
			function (next) {
				plugins.fireHook('filter:user.updateProfile', {uid: uid, data: data, fields: fields}, next);
			},
			function (data, next) {
				fields = data.fields;
				data = data.data;
 
				async.series([
					async.apply(isAboutMeValid, data),
					async.apply(isSignatureValid, data),
					async.apply(isEmailAvailable, data, uid),
					async.apply(isUsernameAvailable, data, uid),
					async.apply(isGroupTitleValid, data)
				], function (err) {
					next(err);
				});
			},
			function (next) {
				async.each(fields, function (field, next) {
					if (!(data[field] !== undefined && typeof data[field] === 'string')) {
						return next();
					}
 
					data[field] = data[field].trim();
 
					if (field === 'email') {
						return updateEmail(uid, data.email, next);
					} else if (field === 'username') {
						return updateUsername(uid, data.username, next);
					} else if (field === 'fullname') {
						return updateFullname(uid, data.fullname, next);
					} else if (field === 'signature') {
						data[field] = S(data[field]).stripTags().s;
					}
 
					User.setUserField(uid, field, data[field], next);
				}, next);
			},
			function (next) {
				plugins.fireHook('action:user.updateProfile', {data: data, uid: uid});
				User.getUserFields(uid, ['email', 'username', 'userslug', 'picture', 'icon:text', 'icon:bgColor'], next);
			}
		], callback);
	};
 
	function isAboutMeValid(data, callback) {
		if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
			callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
		} else {
			callback();
		}
	}
 
	function isSignatureValid(data, callback) {
		if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
			callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
		} else {
			callback();
		}
	}
 
	function isEmailAvailable(data, uid, callback) {
		if (!data.email) {
			return callback();
		}
 
		if (!utils.isEmailValid(data.email)) {
			return callback(new Error('[[error:invalid-email]]'));
		}
 
		async.waterfall([
			function (next) {
				User.getUserField(uid, 'email', next);
			},
			function (email, next) {
				if (email === data.email) {
					return callback();
				}
				User.email.available(data.email, next);
			},
			function (available, next) {
				next(!available ? new Error('[[error:email-taken]]') : null);
			}
		], callback);
	}
 
	function isUsernameAvailable(data, uid, callback) {
		if (!data.username) {
			return callback();
		}
		data.username = data.username.trim();
		async.waterfall([
			function (next) {
				User.getUserFields(uid, ['username', 'userslug'], next);
			},
			function (userData, next) {
				var userslug = utils.slugify(data.username);
 
				if (data.username.length < meta.config.minimumUsernameLength) {
					return next(new Error('[[error:username-too-short]]'));
				}
 
				if (data.username.length > meta.config.maximumUsernameLength) {
					return next(new Error('[[error:username-too-long]]'));
				}
 
				if (!utils.isUserNameValid(data.username) || !userslug) {
					return next(new Error('[[error:invalid-username]]'));
				}
 
				if (userslug === userData.userslug) {
					return callback();
				}
				User.existsBySlug(userslug, next);
			},
			function (exists, next) {
				next(exists ? new Error('[[error:username-taken]]') : null);
			}
		], callback);
	}
 
	function isGroupTitleValid(data, callback) {
		if (data.groupTitle === 'registered-users' || groups.isPrivilegeGroup(data.groupTitle)) {
			callback(new Error('[[error:invalid-group-title]]'));
		} else {
			callback();
		}
	}
 
	function updateEmail(uid, newEmail, callback) {
		User.getUserFields(uid, ['email', 'picture', 'uploadedpicture'], function (err, userData) {
			if (err) {
				return callback(err);
			}
 
			userData.email = userData.email || '';
 
			if (userData.email === newEmail) {
				return callback();
			}
			async.series([
				async.apply(db.sortedSetRemove, 'email:uid', userData.email.toLowerCase()),
				async.apply(db.sortedSetRemove, 'email:sorted', userData.email.toLowerCase() + ':' + uid)
			], function (err) {
				if (err) {
					return callback(err);
				}
 
				async.parallel([
					function (next) {
						db.sortedSetAdd('email:uid', uid, newEmail.toLowerCase(), next);
					},
					async.apply(db.sortedSetAdd, 'user:' + uid + ':emails', Date.now(), newEmail + ':' + Date.now()),
					function (next) {
						db.sortedSetAdd('email:sorted',  0, newEmail.toLowerCase() + ':' + uid, next);
					},
					function (next) {
						User.setUserField(uid, 'email', newEmail, next);
					},
					function (next) {
						if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && newEmail) {
							User.email.sendValidationEmail(uid, newEmail);
						}
						User.setUserField(uid, 'email:confirmed', 0, next);
					},
					function (next) {
						db.sortedSetAdd('users:notvalidated', Date.now(), uid, next);
					}
				], callback);
			});
		});
	}
 
	function updateUsername(uid, newUsername, callback) {
		if (!newUsername) {
			return callback();
		}
 
		User.getUserFields(uid, ['username', 'userslug'], function (err, userData) {
			if (err) {
				return callback(err);
			}
 
			async.parallel([
				function (next) {
					updateUidMapping('username', uid, newUsername, userData.username, next);
				},
				function (next) {
					var newUserslug = utils.slugify(newUsername);
					updateUidMapping('userslug', uid, newUserslug, userData.userslug, next);
				},
				function (next) {
					async.series([
						async.apply(db.sortedSetRemove, 'username:sorted', userData.username.toLowerCase() + ':' + uid),
						async.apply(db.sortedSetAdd, 'username:sorted', 0, newUsername.toLowerCase() + ':' + uid),
						async.apply(db.sortedSetAdd, 'user:' + uid + ':usernames', Date.now(), newUsername + ':' + Date.now())
					], next);
				},
			], callback);
		});
	}
 
	function updateUidMapping(field, uid, value, oldValue, callback) {
		if (value === oldValue) {
			return callback();
		}
 
		async.series([
			function (next) {
				db.sortedSetRemove(field + ':uid', oldValue, next);
			},
			function (next) {
				User.setUserField(uid, field, value, next);
			},
			function (next) {
				if (value) {
					db.sortedSetAdd(field + ':uid', uid, value, next);
				} else {
					next();
				}
			}
		], callback);
	}
 
	function updateFullname(uid, newFullname, callback) {
		async.waterfall([
			function (next) {
				User.getUserField(uid, 'fullname', next);
			},
			function (fullname, next) {
				updateUidMapping('fullname', uid, newFullname, fullname, next);
			}
		], callback);
	}
 
	User.changePassword = function (uid, data, callback) {
		if (!uid || !data || !data.uid) {
			return callback(new Error('[[error:invalid-uid]]'));
		}
 
		async.waterfall([
			function (next) {
				User.isPasswordValid(data.newPassword, next);
			},
			function (next) {
				if (parseInt(uid, 10) !== parseInt(data.uid, 10)) {
					User.isAdministrator(uid, next);
				} else {
					User.isPasswordCorrect(uid, data.currentPassword, next);
				}
			},
			function (isAdminOrPasswordMatch, next) {
				if (!isAdminOrPasswordMatch) {
					return next(new Error('[[error:change_password_error_wrong_current]]'));
				}
 
				User.hashPassword(data.newPassword, next);
			},
			function (hashedPassword, next) {
				async.parallel([
					async.apply(User.setUserField, data.uid, 'password', hashedPassword),
					async.apply(User.reset.updateExpiry, data.uid)
				], function (err) {
					next(err);
				});
			}
		], callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/reset.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/reset.js

Statements: 3.23% (2 / 62)      Branches: 0% (0 / 24)      Functions: 0% (0 / 31)      Lines: 3.23% (2 / 62)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171    2                                                                                   1                                                                                                                                                                                                                                                            
'use strict';
 
var async = require('async'),
	nconf = require('nconf'),
	winston = require('winston'),
 
	user = require('../user'),
	utils = require('../../public/src/utils'),
	translator = require('../../public/src/modules/translator'),
 
	db = require('../database'),
	meta = require('../meta'),
	emailer = require('../emailer');
 
(function (UserReset) {
	var twoHours = 7200000;
 
	UserReset.validate = function (code, callback) {
		async.waterfall([
			function (next) {
				db.getObjectField('reset:uid', code, next);
			},
			function (uid, next) {
				if (!uid) {
					return callback(null, false);
				}
				db.sortedSetScore('reset:issueDate', code, next);
			},
			function (issueDate, next) {
				next(null, parseInt(issueDate, 10) > Date.now() - twoHours);
			}
		], callback);
	};
 
	UserReset.generate = function (uid, callback) {
		var code = utils.generateUUID();
		async.parallel([
			async.apply(db.setObjectField, 'reset:uid', code, uid),
			async.apply(db.sortedSetAdd, 'reset:issueDate', Date.now(), code)
		], function (err) {
			callback(err, code);
		});
	};
 
	function canGenerate(uid, callback) {
		async.waterfall([
			function (next) {
				db.sortedSetScore('reset:issueDate:uid', uid, next);
			},
			function (score, next) {
				if (score > Date.now() - 1000 * 60) {
					return next(new Error('[[error:cant-reset-password-more-than-once-a-minute]]'));
				}
				next();
			}
		], callback);
	}
 
	UserReset.send = function (email, callback) {
		var uid;
		async.waterfall([
			function (next) {
				user.getUidByEmail(email, next);
			},
			function (_uid, next) {
				if (!_uid) {
					return next(new Error('[[error:invalid-email]]'));
				}
 
				uid = _uid;
				canGenerate(uid, next);
			},
			function (next) {
				db.sortedSetAdd('reset:issueDate:uid', Date.now(), uid, next);
			},
			function (next) {
				UserReset.generate(uid, next);
			},
			function (code, next) {
				translator.translate('[[email:password-reset-requested, ' + (meta.config.title || 'NodeBB') + ']]', meta.config.defaultLang, function (subject) {
					next(null, subject, code);
				});
			},
			function (subject, code, next) {
				var reset_link = nconf.get('url') + '/reset/' + code;
				emailer.send('reset', uid, {
					site_title: (meta.config.title || 'NodeBB'),
					reset_link: reset_link,
					subject: subject,
					template: 'reset',
					uid: uid
				}, next);
			}
		], callback);
	};
 
	UserReset.commit = function (code, password, callback) {
		var uid;
		async.waterfall([
			function (next) {
				user.isPasswordValid(password, next);
			},
			function (next) {
				UserReset.validate(code, next);
			},
			function (validated, next) {
				if (!validated) {
					return next(new Error('[[error:reset-code-not-valid]]'));
				}
				db.getObjectField('reset:uid', code, next);
			},
			function (_uid, next) {
				uid = _uid;
				if (!uid) {
					return next(new Error('[[error:reset-code-not-valid]]'));
				}
 
				user.hashPassword(password, next);
			},
			function (hash, next) {
				async.parallel([
					async.apply(user.setUserField, uid, 'password', hash),
					async.apply(db.deleteObjectField, 'reset:uid', code),
					async.apply(db.sortedSetRemove, 'reset:issueDate', code),
					async.apply(db.sortedSetRemove, 'reset:issueDate:uid', uid),
					async.apply(user.reset.updateExpiry, uid),
					async.apply(user.auth.resetLockout, uid)
				], next);
			}
		], callback);
	};
 
	UserReset.updateExpiry = function (uid, callback) {
		var oneDay = 1000 * 60 * 60 * 24;
		var expireDays = parseInt(meta.config.passwordExpiryDays || 0, 10);
		var expiry = Date.now() + (oneDay * expireDays);
 
		callback = callback || function () {};
		user.setUserField(uid, 'passwordExpiry', expireDays > 0 ? expiry : 0, callback);
	};
 
	UserReset.clean = function (callback) {
		async.waterfall([
			function (next) {
				async.parallel({
					tokens: function (next) {
						db.getSortedSetRangeByScore('reset:issueDate', 0, -1, '-inf', Date.now() - twoHours, next);
					},
					uids: function (next) {
						db.getSortedSetRangeByScore('reset:issueDate:uid', 0, -1, '-inf', Date.now() - twoHours, next);
					}
				}, next);
			},
			function (results, next) {
				if (!results.tokens.length && !results.uids.length) {
					return next();
				}
 
				winston.verbose('[UserReset.clean] Removing ' + results.tokens.length + ' reset tokens from database');
				async.parallel([
					async.apply(db.deleteObjectFields, 'reset:uid', results.tokens),
					async.apply(db.sortedSetRemove, 'reset:issueDate', results.tokens),
					async.apply(db.sortedSetRemove, 'reset:issueDate:uid', results.uids)
				], next);
			}
		], callback);
	};
 
}(exports));
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/search.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/search.js

Statements: 6.67% (6 / 90)      Branches: 0% (0 / 58)      Functions: 0% (0 / 23)      Lines: 6.67% (6 / 90)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166      2 2                                                                                                               1                                             1                                                                                             1                                 1                                    
 
'use strict';
 
var async = require('async');
var meta = require('../meta');
var plugins = require('../plugins');
var db = require('../database');
 
module.exports = function (User) {
 
	User.search = function (data, callback) {
		var query = data.query || '';
		var searchBy = data.searchBy || 'username';
		var page = data.page || 1;
		var uid = data.uid || 0;
		var paginate = data.hasOwnProperty('paginate') ? data.paginate : true;
 
		if (searchBy === 'ip') {
			return searchByIP(query, uid, callback);
		}
 
		var startTime = process.hrtime();
 
		var searchResult = {};
		async.waterfall([
			function (next) {
				if (data.findUids) {
					data.findUids(query, searchBy, next);
				} else {
					findUids(query, searchBy, next);
				}
			},
			function (uids, next) {
				filterAndSortUids(uids, data, next);
			},
			function (uids, next) {
				plugins.fireHook('filter:users.search', {uids: uids, uid: uid}, next);
			},
			function (data, next) {
				var uids = data.uids;
				searchResult.matchCount = uids.length;
 
				if (paginate) {
					var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20;
					var start = Math.max(0, page - 1) * resultsPerPage;
					var stop = start + resultsPerPage;
					searchResult.pageCount = Math.ceil(uids.length / resultsPerPage);
					uids = uids.slice(start, stop);
				}
 
				User.getUsers(uids, uid, next);
			},
			function (userData, next) {
				searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2);
				searchResult.users = userData;
				next(null, searchResult);
			}
		], callback);
	};
 
	function findUids(query, searchBy, callback) {
		if (!query) {
			return callback(null, []);
		}
		query = query.toLowerCase();
		var min = query;
		var max = query.substr(0, query.length - 1) + String.fromCharCode(query.charCodeAt(query.length - 1) + 1);
 
		var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20;
		var hardCap = resultsPerPage * 10;
 
		db.getSortedSetRangeByLex(searchBy + ':sorted', min, max, 0, hardCap, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			var uids = data.map(function (data) {
				return data.split(':')[1];
			});
			callback(null, uids);
		});
	}
 
	function filterAndSortUids(uids, data, callback) {
		var sortBy = data.sortBy || 'joindate';
 
		var fields = ['uid', sortBy];
		if (data.onlineOnly) {
			fields = fields.concat(['status', 'lastonline']);
		}
		if (data.bannedOnly) {
			fields.push('banned');
		}
		if (data.flaggedOnly) {
			fields.push('flags');
		}
 
		User.getUsersFields(uids, fields, function (err, userData) {
			if (err) {
				return callback(err);
			}
 
			if (data.onlineOnly) {
				userData = userData.filter(function (user) {
					return user && user.status !== 'offline' && (Date.now() - parseInt(user.lastonline, 10) < 300000);
				});
			}
 
			if (data.bannedOnly) {
				userData = userData.filter(function (user) {
					return user && user.banned;
				});
			}
 
			if (data.flaggedOnly) {
				userData = userData.filter(function (user) {
					return user && parseInt(user.flags, 10) > 0;
				});
			}
 
			sortUsers(userData, sortBy);
 
			uids = userData.map(function (user) {
				return user && user.uid;
			});
 
			callback(null, uids);
		});
	}
 
	function sortUsers(userData, sortBy) {
		if (sortBy === 'joindate' || sortBy === 'postcount' || sortBy === 'reputation') {
			userData.sort(function (u1, u2) {
				return u2[sortBy] - u1[sortBy];
			});
		} else {
			userData.sort(function (u1, u2) {
				if(u1[sortBy] < u2[sortBy]) {
					return -1;
				} else if(u1[sortBy] > u2[sortBy]) {
					return 1;
				}
				return 0;
			});
		}
	}
 
	function searchByIP(ip, uid, callback) {
		var start = process.hrtime();
		async.waterfall([
			function (next) {
				db.getSortedSetRevRange('ip:' + ip + ':uid', 0, -1, next);
			},
			function (uids, next) {
				User.getUsers(uids, uid, next);
			},
			function (users, next) {
				var diff = process.hrtime(start);
				var timing = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(1);
				next(null, {timing: timing, users: users});
			}
		], callback);
	}
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/settings.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/settings.js

Statements: 5% (4 / 80)      Branches: 0% (0 / 65)      Functions: 0% (0 / 18)      Lines: 5% (4 / 80)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167      2 2                                                                                         1                                                                     1                                                                                                                                                                    
 
'use strict';
 
var async = require('async');
var meta = require('../meta');
var db = require('../database');
var plugins = require('../plugins');
 
module.exports = function (User) {
 
	User.getSettings = function (uid, callback) {
		if (!parseInt(uid, 10)) {
			return onSettingsLoaded(0, {}, callback);
		}
 
		db.getObject('user:' + uid + ':settings', function (err, settings) {
			if (err) {
				return callback(err);
			}
 
			onSettingsLoaded(uid, settings ? settings : {}, callback);
		});
	};
 
	User.getMultipleUserSettings = function (uids, callback) {
		if (!Array.isArray(uids) || !uids.length) {
			return callback(null, []);
		}
 
		var keys = uids.map(function (uid) {
			return 'user:' + uid + ':settings';
		});
 
		db.getObjects(keys, function (err, settings) {
			if (err) {
				return callback(err);
			}
 
			for (var i = 0; i < settings.length; ++i) {
				settings[i] = settings[i] || {};
				settings[i].uid = uids[i];
			}
 
			async.map(settings, function (setting, next) {
				onSettingsLoaded(setting.uid, setting, next);
			}, callback);
		});
	};
 
	function onSettingsLoaded(uid, settings, callback) {
		plugins.fireHook('filter:user.getSettings', {uid: uid, settings: settings}, function (err, data) {
			if (err) {
				return callback(err);
			}
 
			settings = data.settings;
 
			var defaultTopicsPerPage = parseInt(meta.config.topicsPerPage, 10) || 20;
			var defaultPostsPerPage = parseInt(meta.config.postsPerPage, 10) || 20;
 
			settings.showemail = parseInt(getSetting(settings, 'showemail', 0), 10) === 1;
			settings.showfullname = parseInt(getSetting(settings, 'showfullname', 0), 10) === 1;
			settings.openOutgoingLinksInNewTab = parseInt(getSetting(settings, 'openOutgoingLinksInNewTab', 0), 10) === 1;
			settings.dailyDigestFreq = getSetting(settings, 'dailyDigestFreq', 'off');
			settings.usePagination = parseInt(getSetting(settings, 'usePagination', 0), 10) === 1;
			settings.topicsPerPage = Math.min(settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : defaultTopicsPerPage, defaultTopicsPerPage);
			settings.postsPerPage = Math.min(settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : defaultPostsPerPage, defaultPostsPerPage);
			settings.userLang = settings.userLang || meta.config.defaultLang || 'en-GB';
			settings.topicPostSort = getSetting(settings, 'topicPostSort', 'oldest_to_newest');
			settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'newest_to_oldest');
			settings.followTopicsOnCreate = parseInt(getSetting(settings, 'followTopicsOnCreate', 1), 10) === 1;
			settings.followTopicsOnReply = parseInt(getSetting(settings, 'followTopicsOnReply', 0), 10) === 1;
			settings.sendChatNotifications = parseInt(getSetting(settings, 'sendChatNotifications', 0), 10) === 1;
			settings.sendPostNotifications = parseInt(getSetting(settings, 'sendPostNotifications', 0), 10) === 1;
			settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
			settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
			settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1;
			settings.bootswatchSkin = settings.bootswatchSkin || 'default';
			settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
 
			callback(null, settings);
		});
	}
 
	function getSetting(settings, key, defaultValue) {
		if (settings[key] || settings[key] === 0) {
			return settings[key];
		} else if (meta.config[key] || meta.config[key] === 0) {
			return meta.config[key];
		}
		return defaultValue;
	}
 
	User.saveSettings = function (uid, data, callback) {
		if (!data.postsPerPage || parseInt(data.postsPerPage, 10) <= 1 || parseInt(data.postsPerPage, 10) > meta.config.postsPerPage) {
			return callback(new Error('[[error:invalid-pagination-value, 2, ' + meta.config.postsPerPage + ']]'));
		}
 
		if (!data.topicsPerPage || parseInt(data.topicsPerPage, 10) <= 1 || parseInt(data.topicsPerPage, 10) > meta.config.topicsPerPage) {
			return callback(new Error('[[error:invalid-pagination-value, 2, ' + meta.config.topicsPerPage + ']]'));
		}
 
		data.userLang = data.userLang || meta.config.defaultLang;
 
		plugins.fireHook('action:user.saveSettings', {uid: uid, settings: data});
 
		var settings = {
			showemail: data.showemail,
			showfullname: data.showfullname,
			openOutgoingLinksInNewTab: data.openOutgoingLinksInNewTab,
			dailyDigestFreq: data.dailyDigestFreq || 'off',
			usePagination: data.usePagination,
			topicsPerPage: Math.min(data.topicsPerPage, parseInt(meta.config.topicsPerPage, 10) || 20),
			postsPerPage: Math.min(data.postsPerPage, parseInt(meta.config.postsPerPage, 10) || 20),
			userLang: data.userLang || meta.config.defaultLang,
			followTopicsOnCreate: data.followTopicsOnCreate,
			followTopicsOnReply: data.followTopicsOnReply,
			sendChatNotifications: data.sendChatNotifications,
			sendPostNotifications: data.sendPostNotifications,
			restrictChat: data.restrictChat,
			topicSearchEnabled: data.topicSearchEnabled,
			delayImageLoading: data.delayImageLoading,
			homePageRoute : ((data.homePageRoute === 'custom' ? data.homePageCustom : data.homePageRoute) || '').replace(/^\//, ''),
			scrollToMyPost: data.scrollToMyPost,
			notificationSound: data.notificationSound,
			incomingChatSound: data.incomingChatSound,
			outgoingChatSound: data.outgoingChatSound
		};
 
		if (data.bootswatchSkin) {
			settings.bootswatchSkin = data.bootswatchSkin;
		}
 
		async.waterfall([
			function (next) {
				db.setObject('user:' + uid + ':settings', settings, next);
			},
			function (next) {
				User.updateDigestSetting(uid, data.dailyDigestFreq, next);
			},
			function (next) {
				User.getSettings(uid, next);
			}
		], callback);
	};
 
	User.updateDigestSetting = function (uid, dailyDigestFreq, callback) {
		async.waterfall([
			function (next) {
				db.sortedSetsRemove(['digest:day:uids', 'digest:week:uids', 'digest:month:uids'], uid, next);
			},
			function (next) {
				if (['day', 'week', 'month'].indexOf(dailyDigestFreq) !== -1) {
					db.sortedSetAdd('digest:' + dailyDigestFreq + ':uids', Date.now(), uid, next);
				} else {
					next();
				}
			}
		], callback);
	};
 
	User.setSetting = function (uid, key, value, callback) {
		db.setObjectField('user:' + uid + ':settings', key, value, callback);
	};
};
 
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/topics.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/user/topics.js

Statements: 28.57% (2 / 7)      Branches: 100% (0 / 0)      Functions: 0% (0 / 3)      Lines: 28.57% (2 / 7)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20    2 2                                
'use strict';
 
var async = require('async');
var db = require('../database');
 
module.exports = function (User) {
 
	User.getIgnoredTids = function (uid, start, stop, callback) {
		db.getSortedSetRevRange('uid:' + uid + ':ignored_tids', start, stop, callback);
	};
 
	User.addTopicIdToUser = function (uid, tid, timestamp, callback) {
		async.parallel([
			async.apply(db.sortedSetAdd, 'uid:' + uid + ':topics', timestamp, tid),
			async.apply(User.incrementUserFieldBy, uid, 'topiccount', 1)
		], callback);
	};
 
};
 
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/widgets/

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/widgets/

Statements: 4.88% (4 / 82)      Branches: 0% (0 / 53)      Functions: 0% (0 / 22)      Lines: 4.88% (4 / 82)      Ignored: none     

All files » node-npmtest-nodebb/node_modules/nodebb/src/widgets/
File Statements Branches Functions Lines
index.js 4.88% (4 / 82) 0% (0 / 53) 0% (0 / 22) 4.88% (4 / 82)
Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/widgets/index.js

npmtest-nodebb (v0.0.1)

Code coverage report for node-npmtest-nodebb/node_modules/nodebb/src/widgets/index.js

Statements: 4.88% (4 / 82)      Branches: 0% (0 / 53)      Functions: 0% (0 / 22)      Lines: 4.88% (4 / 82)      Ignored: none     

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178    2 2 2   2                                                                                                                                                                                                                                                                                                                                                      
"use strict";
 
var async = require('async');
var winston = require('winston');
var templates = require('templates.js');
 
var plugins = require('../plugins');
var translator = require('../../public/src/modules/translator');
var db = require('../database');
 
var widgets = {};
 
widgets.render = function (uid, area, req, res, callback) {
	if (!area.locations || !area.template) {
		return callback(new Error('[[error:invalid-data]]'));
	}
 
	widgets.getAreas(['global', area.template], area.locations, function (err, data) {
		if (err) {
			return callback(err);
		}
 
		var widgetsByLocation = {};
 
		async.map(area.locations, function (location, done) {
			widgetsByLocation[location] = data.global[location].concat(data[area.template][location]);
 
			if (!widgetsByLocation[location].length) {
				return done(null, {location: location, widgets: []});
			}
 
			async.map(widgetsByLocation[location], function (widget, next) {
				if (!widget || !widget.data ||
					(!!widget.data['hide-registered'] && uid !== 0) ||
					(!!widget.data['hide-guests'] && uid === 0) ||
					(!!widget.data['hide-mobile'] && area.isMobile)) {
					return next();
				}
 
				plugins.fireHook('filter:widget.render:' + widget.widget, {
					uid: uid,
					area: area,
					data: widget.data,
					req: req,
					res: res
				}, function (err, html) {
					if (err || html === null) {
						return next(err);
					}
 
					if (typeof html !== 'string') {
						html = '';
					}
 
					if (widget.data.container && widget.data.container.match('{body}')) {
						translator.translate(widget.data.title, function (title) {
							html = templates.parse(widget.data.container, {
								title: title,
								body: html
							});
 
							next(null, {html: html});
						});
					} else {
						next(null, {html: html});
					}
				});
			}, function (err, result) {
				done(err, {location: location, widgets: result.filter(Boolean)});
			});
		}, callback);
	});
};
 
widgets.getAreas = function (templates, locations, callback) {
	var keys = templates.map(function (tpl) {
		return 'widgets:' + tpl;
	});
	db.getObjectsFields(keys, locations, function (err, data) {
		if (err) {
			return callback(err);
		}
 
		var returnData = {};
 
		templates.forEach(function (template, index) {
			returnData[template] = returnData[template] || {};
			locations.forEach(function (location) {
				if (data && data[index] && data[index][location]) {
					try {
						returnData[template][location] = JSON.parse(data[index][location]);
					} catch(err) {
						winston.error('can not parse widget data. template:  ' + template + ' location: ' + location);
						returnData[template][location] = [];
					}
				} else {
					returnData[template][location] = [];
				}
			});
		});
 
		callback(null, returnData);
	});
};
 
widgets.getArea = function (template, location, callback) {
	db.getObjectField('widgets:' + template, location, function (err, result) {
		if (err) {
			return callback(err);
		}
		if (!result) {
			return callback(null, []);
		}
		try {
			result = JSON.parse(result);
		} catch(err) {
			return callback(err);
		}
 
		callback(null, result);
	});
};
 
widgets.setArea = function (area, callback) {
	if (!area.location || !area.template) {
		return callback(new Error('Missing location and template data'));
	}
 
	db.setObjectField('widgets:' + area.template, area.location, JSON.stringify(area.widgets), callback);
};
 
widgets.reset = function (callback) {
	var defaultAreas = [
		{ name: 'Draft Zone', template: 'global', location: 'header' },
		{ name: 'Draft Zone', template: 'global', location: 'footer' },
		{ name: 'Draft Zone', template: 'global', location: 'sidebar' }
	];
 
	async.parallel({
		areas: function (next) {
			plugins.fireHook('filter:widgets.getAreas', defaultAreas, next);
		},
		drafts: function (next) {
			widgets.getArea('global', 'drafts', next);
		}
	}, function (err, results) {
		if (err) {
			return callback(err);
		}
 
		var drafts = results.drafts || [];
 
		async.each(results.areas, function (area, next) {
			widgets.getArea(area.template, area.location, function (err, areaData) {
				if (err) {
					return next(err);
				}
 
				drafts = drafts.concat(areaData);
				area.widgets = [];
				widgets.setArea(area, next);
			});
		}, function (err) {
			if (err) {
				return callback(err);
			}
			widgets.setArea({
				template: 'global',
				location: 'drafts',
				widgets: drafts
			}, callback);
		});
	});
};
 
module.exports = widgets;